From 32c1e88db4ffe938fa7a8ff1e58d81374b463c99 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 11 Nov 2023 19:47:31 -0500 Subject: [PATCH 1/4] Re-add "npm run changeset" instead of requiring changeset installed globally --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9244907f7..d99576d39 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "build": "rimraf packages/*/dist && pnpm -r build", "lint": "pnpm -r lint", "format": "pnpm -r format", + "changeset": "changeset", "changeset:version": "changeset version", "changeset:release": "changeset publish", "up-dep": "pnpm -r up --latest" From a730969f0a30271b9c6aec84df83a5c9b35e5eaa Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 11 Nov 2023 19:47:49 -0500 Subject: [PATCH 2/4] [ScrollingValue] Add `single` prop to enable individual number display (proper handling of 9) --- .changeset/sixty-mangos-melt.md | 5 +++++ .../src/lib/components/ScrollingValue.svelte | 3 ++- .../docs/components/ScrollingValue/+page.svelte | 14 ++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 .changeset/sixty-mangos-melt.md diff --git a/.changeset/sixty-mangos-melt.md b/.changeset/sixty-mangos-melt.md new file mode 100644 index 000000000..e145b8620 --- /dev/null +++ b/.changeset/sixty-mangos-melt.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': patch +--- + +[ScrollingValue] Add `single` prop to enable individual number display (proper handling of 9) diff --git a/packages/svelte-ux/src/lib/components/ScrollingValue.svelte b/packages/svelte-ux/src/lib/components/ScrollingValue.svelte index d9cdda5f0..f537848f0 100644 --- a/packages/svelte-ux/src/lib/components/ScrollingValue.svelte +++ b/packages/svelte-ux/src/lib/components/ScrollingValue.svelte @@ -6,6 +6,7 @@ import { getComponentTheme } from './theme'; export let value = 0; + export let single = false; export let format: (value: number) => string | number = (value) => value; export let classes: { @@ -33,7 +34,7 @@ class={cls('col-span-full row-span-full', theme.value, classes.value)} style:transform="translateY({-100 + 100 * offset}%)" > - {format(Math.floor($displayValue + 1))} + {format(Math.floor(single && $displayValue >= 9 ? 0 : $displayValue + 1))}
+

Individual numbers

+ + + {#each Math.abs(value).toString().split('') as num} + + {/each} + +

Formatted

@@ -86,12 +94,6 @@ -

Font-size

- - - - -

Field

With label and appended actions

From 8a23f6b0973f298b963ee4b505247c7a701ca31d Mon Sep 17 00:00:00 2001 From: Will Nations Date: Sat, 11 Nov 2023 22:00:19 -0600 Subject: [PATCH 3/4] Fix type errors for Button's use of Icon data. (#125) --- .changeset/slimy-starfishes-report.md | 6 ++++++ .../svelte-ux/src/lib/components/Button.svelte | 5 +++-- .../svelte-ux/src/lib/components/Icon.svelte | 13 ++++++++----- .../src/lib/components/TextField.svelte | 13 +++++++------ packages/svelte-ux/src/lib/utils/icons.ts | 17 +++++++++++++++++ 5 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 .changeset/slimy-starfishes-report.md create mode 100644 packages/svelte-ux/src/lib/utils/icons.ts diff --git a/.changeset/slimy-starfishes-report.md b/.changeset/slimy-starfishes-report.md new file mode 100644 index 000000000..b691f6652 --- /dev/null +++ b/.changeset/slimy-starfishes-report.md @@ -0,0 +1,6 @@ +--- +'svelte-ux': patch +--- + +Added new `IconInput` and `IconData` types to enable inclusive & seamless passing of icon arguments between components. Also provides a `asIconData` utility function for type-safe conversion. +Fixed type errors for Button & TextField's use of Icon data. \ No newline at end of file diff --git a/packages/svelte-ux/src/lib/components/Button.svelte b/packages/svelte-ux/src/lib/components/Button.svelte index 5ec447cfc..5c53299f7 100644 --- a/packages/svelte-ux/src/lib/components/Button.svelte +++ b/packages/svelte-ux/src/lib/components/Button.svelte @@ -10,12 +10,13 @@ import type { TailwindColors } from '$lib/types'; import { getComponentTheme } from './theme'; import { getButtonGroup } from './ButtonGroup.svelte'; + import { asIconData, type IconInput } from '$lib/utils/icons'; export let type: 'button' | 'submit' | 'reset' = 'button'; export let href: string | undefined = undefined; export let target: string | undefined = undefined; export let fullWidth: boolean = false; - export let icon: ComponentProps['data'] | ComponentProps | undefined = undefined; + export let icon: IconInput = undefined; export let iconOnly = icon !== undefined && $$slots.default !== true; export let actions: Actions | undefined = undefined; @@ -261,7 +262,7 @@ {#if typeof icon === 'string' || 'icon' in icon} - + {:else} {/if} diff --git a/packages/svelte-ux/src/lib/components/Icon.svelte b/packages/svelte-ux/src/lib/components/Icon.svelte index 39538dcfc..c29608ee2 100644 --- a/packages/svelte-ux/src/lib/components/Icon.svelte +++ b/packages/svelte-ux/src/lib/components/Icon.svelte @@ -14,7 +14,7 @@ export let height = size; export let viewBox = '0 0 24 24'; export let path: string | string[] = ''; - export let data: IconDefinition | string | undefined = undefined; + export let data: IconDefinition | string | null | undefined = undefined; export let svg: string | undefined = undefined; export let svgUrl: string | undefined = undefined; @@ -31,7 +31,7 @@ } = {}; const theme = getComponentTheme('Icon'); - $: if (typeof data === 'object' && 'icon' in data) { + $: if (typeof data === 'object' && data && 'icon' in data) { // Font Awesome const [_width, _height, _ligatures, _unicode, _path] = data.icon; viewBox = `0 0 ${_width} ${_height}`; @@ -59,15 +59,18 @@ $: if (svgUrl) { let promise; if (cache.has(svgUrl)) { - cache.get(svgUrl).then((text) => (svg = text)); + cache.get(svgUrl)?.then((text) => (svg = text)); } else { promise = fetch(svgUrl) .then((resp) => resp.text()) - .catch((e) => { + .catch(() => { // Failed request, remove promise so fetched again - cache.delete(svgUrl); + if (svgUrl && typeof(svgUrl) === "string") { + cache.delete(svgUrl); + } // TODO: Consider showing error icon // throw e; + return ""; }); cache.set(svgUrl, promise); promise.then((text) => { diff --git a/packages/svelte-ux/src/lib/components/TextField.svelte b/packages/svelte-ux/src/lib/components/TextField.svelte index 92ed6184d..e4c5cb8ff 100644 --- a/packages/svelte-ux/src/lib/components/TextField.svelte +++ b/packages/svelte-ux/src/lib/components/TextField.svelte @@ -14,12 +14,13 @@ import Button from './Button.svelte'; import Icon from './Icon.svelte'; import Input from './Input.svelte'; + import { type IconInput, asIconData } from '$lib/utils/icons'; type InputValue = string | number; const dispatch = createEventDispatcher<{ clear: null; - change: { value: typeof value; inputValue: InputValue; operator: string }; + change: { value: typeof value; inputValue: InputValue; operator?: string }; }>(); export let name: string | undefined = undefined; @@ -45,8 +46,8 @@ export let base = false; export let rounded = false; export let dense = false; - export let icon: string | null = null; - export let iconRight: string | null = null; + export let icon: IconInput = null; + export let iconRight: IconInput = null; export let align: 'left' | 'center' | 'right' = 'left'; export let autofocus: boolean | Parameters[1] = false; // TODO: Find way to conditionally set type based on `multiline` value @@ -176,7 +177,7 @@ $: hasInputValue = inputValue != null && inputValue !== ''; $: hasInsetLabel = ['inset', 'float'].includes(labelPlacement) && label !== ''; - $: hasPrepend = $$slots.prepend || icon != null; + $: hasPrepend = $$slots.prepend || !!icon; $: hasAppend = $$slots.append || iconRight != null || clearable || error || operators || type === 'password'; $: hasPrefix = $$slots.prefix || type === 'currency'; @@ -247,7 +248,7 @@ {#if icon} - + {/if}
@@ -417,7 +418,7 @@ {#if error} {:else if iconRight} - + {/if} {/if} diff --git a/packages/svelte-ux/src/lib/utils/icons.ts b/packages/svelte-ux/src/lib/utils/icons.ts new file mode 100644 index 000000000..a862ed49b --- /dev/null +++ b/packages/svelte-ux/src/lib/utils/icons.ts @@ -0,0 +1,17 @@ + +import type { ComponentProps } from "svelte"; +import type { default as Icon } from "$lib/components/Icon.svelte"; +import { isLiteralObject } from "./object"; + +export type IconInput = ComponentProps['data'] | ComponentProps; +export type IconData = ComponentProps['data']; + +export function asIconData(v: IconInput): IconData { + return isIconComponentProps(v) ? v.data : v; +} + +function isIconComponentProps(v: any): v is ComponentProps { + // `iconName` is a required property of `IconDefinition`, the only other object that `IconInput` supports. + // If it is undefined, then only ComponentProps is viable. + return isLiteralObject(v) && typeof(v['iconName']) === "undefined"; +} From 3c9bbea1208dc2fa0b828d924b2c5a9d5965df2d Mon Sep 17 00:00:00 2001 From: willnationsdev Date: Wed, 8 Nov 2023 00:33:18 -0600 Subject: [PATCH 4/4] Replace SelectList with SelectField. - Updates QuickSearch to use SelectField. --- .changeset/quick-avocados-hope.md | 5 + .../src/lib/components/QuickSearch.svelte | 13 +- .../src/lib/components/SelectField.svelte | 51 ++- .../src/lib/components/SelectList.svelte | 420 ------------------ .../svelte-ux/src/lib/components/index.ts | 1 - 5 files changed, 54 insertions(+), 436 deletions(-) create mode 100644 .changeset/quick-avocados-hope.md delete mode 100644 packages/svelte-ux/src/lib/components/SelectList.svelte diff --git a/.changeset/quick-avocados-hope.md b/.changeset/quick-avocados-hope.md new file mode 100644 index 000000000..3c53722a6 --- /dev/null +++ b/.changeset/quick-avocados-hope.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': minor +--- + +Removes SelectList. Updates SelectField features to support SelectList's use case via property/attribute overrides. Updates QuickSearch to use SelectField. diff --git a/packages/svelte-ux/src/lib/components/QuickSearch.svelte b/packages/svelte-ux/src/lib/components/QuickSearch.svelte index c947973e7..cdef197e8 100644 --- a/packages/svelte-ux/src/lib/components/QuickSearch.svelte +++ b/packages/svelte-ux/src/lib/components/QuickSearch.svelte @@ -3,10 +3,11 @@ import Button from '$lib/components/Button.svelte'; import Dialog from '$lib/components/Dialog.svelte'; - import SelectList from '$lib/components/SelectList.svelte'; + import SelectField from '$lib/components/SelectField.svelte'; import { getComponentTheme } from './theme'; import { cls } from '$lib/utils/styles'; import { smScreen } from '$lib/stores'; + import { autoFocus, selectOnFocus } from '$lib/actions'; export let options: { name: string; value: string; group?: string }[] = []; @@ -18,10 +19,11 @@ let open = false; + let fieldActions = (node: any) => [autoFocus(node), selectOnFocus(node)]; + /* TODO: - [ ] Sticky search - - [ ] Refine SelectList / reuse with SelectField (and maybe MultiSelect) - [ ] Improve size of Dialog (move class to Dialog without breaking overflow) - [ ] Load descriptions/meta from +page.ts - [ ] Improve dialog positioning on small viewports (consistent top/bottom with max height) @@ -62,12 +64,15 @@ }} >
- (open = false)} + on:change={() => (open = false)} classes={{ field: { container: 'border-none hover:shadow-none group-focus-within:shadow-none', diff --git a/packages/svelte-ux/src/lib/components/SelectField.svelte b/packages/svelte-ux/src/lib/components/SelectField.svelte index b0c78e0dd..e3cda9030 100644 --- a/packages/svelte-ux/src/lib/components/SelectField.svelte +++ b/packages/svelte-ux/src/lib/components/SelectField.svelte @@ -5,7 +5,7 @@ import { mdiChevronDown, mdiClose } from '@mdi/js'; import Logger from '../utils/logger'; - import { selectOnFocus } from '../actions/input'; + import { autoFocus, selectOnFocus } from '../actions/input'; import { cls } from '../utils/styles'; import Button from './Button.svelte'; @@ -14,6 +14,8 @@ import MenuItem from './MenuItem.svelte'; import TextField from './TextField.svelte'; import { getComponentTheme } from './theme'; + import Maybe from './Maybe.svelte'; + import type { IconInput } from '$lib/utils/icons'; const dispatch = createEventDispatcher<{ change: { value: any; option: any }; @@ -31,16 +33,26 @@ export let loading: boolean = false; export let disabled: boolean = false; export let readonly: boolean = false; - export let icon: string | null = null; + export let icon: IconInput = undefined; + export let toggleIcon: IconInput = mdiChevronDown; + export let closeIcon: IconInput = mdiClose; export let clearable = true; export let base = false; export let rounded = false; export let dense = false; export let clearSearchOnOpen = true; + export let tabIterable = false; + export let tabindex = 0; + export let autofocus: ComponentProps['autofocus'] = undefined; + export let fieldActions: ComponentProps['actions'] = autofocus + ? (node) => [autoFocus(node, typeof autofocus === 'object' ? autofocus : undefined), selectOnFocus(node)] + : undefined; + + export let showToggleIcon = true; export let classes: { root?: string; - field?: string; + field?: string | ComponentProps['classes']; options?: string; option?: string; selected?: string; @@ -49,6 +61,9 @@ } = {}; const theme = getComponentTheme('SelectField'); + let fieldClasses: ComponentProps['classes']; + $: fieldClasses = typeof(classes.field) === "string" ? { root: classes.field } : classes.field; + // Menu props export let placement: Placement = 'bottom-start'; export let autoPlacement = true; @@ -56,6 +71,7 @@ export let resize = true; export let disableTransition = false; export let menuProps: ComponentProps | undefined = undefined; + export let optionsMode: "list" | "menu" = "menu"; $: filteredOptions = options ?? []; let searchText = ''; @@ -336,7 +352,14 @@ -
+
+ [selectOnFocus(node)]} - class={cls('h-full', theme.field, classes.field)} + actions={fieldActions} + classes={{ + root: cls('h-full'), + ...theme.field, + ...fieldClasses, + }} role="combobox" aria-expanded={open ? "true" : "false"} + aria-autocomplete={optionsMode === "menu" ? "list" : undefined} {...$$restProps} > @@ -371,16 +399,16 @@ {:else if value && clearable}
-
+ {/if}
diff --git a/packages/svelte-ux/src/lib/components/SelectList.svelte b/packages/svelte-ux/src/lib/components/SelectList.svelte deleted file mode 100644 index fe970e716..000000000 --- a/packages/svelte-ux/src/lib/components/SelectList.svelte +++ /dev/null @@ -1,420 +0,0 @@ - - - -
- [autoFocus(node), selectOnFocus(node)]} - class={cls('h-full')} - classes={{ ...theme.field, ...classes.field }} - {...$$restProps} - > - - - - - - {#if loading} - - - - {:else if readonly} - - {:else if value && clearable} -
diff --git a/packages/svelte-ux/src/lib/components/index.ts b/packages/svelte-ux/src/lib/components/index.ts index 4f8610c91..47e9b8a1d 100644 --- a/packages/svelte-ux/src/lib/components/index.ts +++ b/packages/svelte-ux/src/lib/components/index.ts @@ -68,7 +68,6 @@ export { default as ScrollingValue } from './ScrollingValue.svelte'; export { default as SectionDivider } from './SectionDivider.svelte'; export { default as Selection } from './Selection.svelte'; export { default as SelectField } from './SelectField.svelte'; -export { default as SelectList } from './SelectList.svelte'; export { default as Shine } from './Shine.svelte'; export { default as SpringValue } from './SpringValue.svelte'; export { default as Stack } from './Stack.svelte';