From dc16d9dda507da213a49109f0ddb3632cde2029b Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Tue, 2 Jul 2024 12:22:04 -0400 Subject: [PATCH 1/4] Pass object of classes through Select-like fields to Field instances --- .../lib/components/MultiSelectField.svelte | 10 ++++++--- .../src/lib/components/SelectField.svelte | 18 +++++++++------- .../svelte-ux/src/lib/types/typeHelpers.ts | 4 ++-- packages/svelte-ux/src/lib/utils/styles.ts | 21 +++++++++++++++++-- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/packages/svelte-ux/src/lib/components/MultiSelectField.svelte b/packages/svelte-ux/src/lib/components/MultiSelectField.svelte index ccfbc59b8..764f420bb 100644 --- a/packages/svelte-ux/src/lib/components/MultiSelectField.svelte +++ b/packages/svelte-ux/src/lib/components/MultiSelectField.svelte @@ -11,7 +11,7 @@ import MultiSelectOption from './MultiSelectOption.svelte'; import TextField from './TextField.svelte'; - import { cls } from '../utils/styles.js'; + import { cls, clsMerge, normalizeClasses } from '../utils/styles.js'; import { Logger } from '../utils/logger.js'; import ProgressCircle from './ProgressCircle.svelte'; @@ -47,7 +47,7 @@ export let classes: { root?: string; multiSelectMenu?: MultiSelectMenuProps['classes']; - field?: string; + field?: string | ComponentProps['classes']; actions?: string; } = {}; @@ -154,7 +154,11 @@ bind:inputEl on:focus={onFocus} on:change={onSearchChange} - class={cls('h-full', settingsClasses.field, classes.field)} + classes={clsMerge( + normalizeClasses(settingsClasses.field), + { root: 'h-full' }, + normalizeClasses(classes.field), + )} {...restProps} > diff --git a/packages/svelte-ux/src/lib/components/SelectField.svelte b/packages/svelte-ux/src/lib/components/SelectField.svelte index 4641dc1fc..9d27856d2 100644 --- a/packages/svelte-ux/src/lib/components/SelectField.svelte +++ b/packages/svelte-ux/src/lib/components/SelectField.svelte @@ -6,7 +6,7 @@ import { Logger } from '../utils/logger.js'; import { autoFocus, selectOnFocus } from '../actions/input.js'; - import { cls } from '../utils/styles.js'; + import { cls, clsMerge, normalizeClasses } from '../utils/styles.js'; import Button from './Button.svelte'; import ProgressCircle from './ProgressCircle.svelte'; @@ -427,12 +427,16 @@ on:keydown={onKeyDown} on:keypress={onKeyPress} actions={fieldActions} - classes={{ - container: inlineOptions - ? 'border-none shadow-none hover:shadow-none group-focus-within:shadow-none' - : undefined, - }} - class={cls('h-full', settingsClasses.field, fieldClasses)} + classes={clsMerge( + normalizeClasses(settingsClasses.field), + { + root: 'h-full', + container: inlineOptions + ? 'border-none shadow-none hover:shadow-none group-focus-within:shadow-none' + : undefined, + }, + normalizeClasses(classes.field), + )} role="combobox" aria-expanded={open ? 'true' : 'false'} aria-autocomplete={!inlineOptions ? 'list' : undefined} diff --git a/packages/svelte-ux/src/lib/types/typeHelpers.ts b/packages/svelte-ux/src/lib/types/typeHelpers.ts index e51ad7cba..0872e1729 100644 --- a/packages/svelte-ux/src/lib/types/typeHelpers.ts +++ b/packages/svelte-ux/src/lib/types/typeHelpers.ts @@ -41,10 +41,10 @@ export function keys(o: T) { export type ObjectKey = string | number | symbol; // Get entries (array of [key, value] arrays) of object (strongly-typed) -export function entries(o: Record): [K, V][]; +export function entries(o: Record): [`${Extract}`, V][]; export function entries(o: Map): [K, V][]; // @ts-expect-error -export function entries(o: Record | Map): [K, V][] { +export function entries(o: Record | Map) { if (o instanceof Map) return Array.from(o.entries()) as unknown as [K, V][]; return Object.entries(o) as unknown as [K, V][]; // TODO: Improve based on key/value pair - https://stackoverflow.com/questions/60141960/typescript-key-value-relation-preserving-object-entries-type } diff --git a/packages/svelte-ux/src/lib/utils/styles.ts b/packages/svelte-ux/src/lib/utils/styles.ts index 550a31f08..7972ef8ca 100644 --- a/packages/svelte-ux/src/lib/utils/styles.ts +++ b/packages/svelte-ux/src/lib/utils/styles.ts @@ -2,6 +2,7 @@ import clsx, { type ClassValue } from 'clsx'; import { extendTailwindMerge } from 'tailwind-merge'; import { range } from 'd3-array'; import { entries } from '../types/typeHelpers.js'; +import { mergeWith } from 'lodash-es'; /** * Convert object to style string @@ -11,7 +12,6 @@ export function objectToString(styleObj: { [key: string]: string }) { .map(([key, value]) => { if (value) { // Convert camelCase into kaboob-case (ex. (transformOrigin => transform-origin)) - // @ts-expect-error const propertyName = key.replace(/([A-Z])/g, '-$1').toLowerCase(); return `${propertyName}: ${value};`; } else { @@ -40,4 +40,21 @@ const twMerge = extendTailwindMerge({ }, }); -export const cls = (...inputs: ClassValue[]) => twMerge(clsx(...inputs)); +type ClassFalsyValues = undefined | null | false; +type AnyClassValue = ClassValue | ClassFalsyValues; +type AnyClassCollection = + | Record + | ClassFalsyValues; + +export const cls = (...inputs: AnyClassValue[]) => twMerge(clsx(...inputs)); + +export const clsMerge = ( + ...inputs: T[] +): Exclude => + mergeWith({}, ...inputs.filter(Boolean), (a: string, b: string) => + twMerge(a, b) + ); + +export const normalizeClasses = (classes: string | ClassFalsyValues | T): T => { + return classes && typeof classes === 'object' ? classes : { root: classes } as T; +} From a5153b7836ee5f19d77771805e0020ee52350cc1 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Tue, 2 Jul 2024 13:06:08 -0400 Subject: [PATCH 2/4] Add changeset(s) --- .changeset/friendly-squids-attack1.md | 5 +++++ .changeset/friendly-squids-attack2.md | 5 +++++ .changeset/friendly-squids-attack3.md | 5 +++++ .changeset/friendly-squids-attack4.md | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 .changeset/friendly-squids-attack1.md create mode 100644 .changeset/friendly-squids-attack2.md create mode 100644 .changeset/friendly-squids-attack3.md create mode 100644 .changeset/friendly-squids-attack4.md diff --git a/.changeset/friendly-squids-attack1.md b/.changeset/friendly-squids-attack1.md new file mode 100644 index 000000000..0294917df --- /dev/null +++ b/.changeset/friendly-squids-attack1.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': patch +--- + +adds a function `normalizeClasses` to normalize classes when they can be a string or an object, essentially converting strings to objects (e.g. `'p-2'` ➞ `{ root: 'p-2' }`) diff --git a/.changeset/friendly-squids-attack2.md b/.changeset/friendly-squids-attack2.md new file mode 100644 index 000000000..5143ed437 --- /dev/null +++ b/.changeset/friendly-squids-attack2.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': patch +--- + +adds a function `clsMerge` for merging groups of classes without needing to do more complex property-by-property merging (e.g. `{ root: cls(settingsClasses.root, 'grid', classes.root), field: cls(settingsClasses.field, 'flex', classes.field) }` ➞ `clsMerge(settingsClasses, { root: 'grid', field: 'flex' }, classes)`) diff --git a/.changeset/friendly-squids-attack3.md b/.changeset/friendly-squids-attack3.md new file mode 100644 index 000000000..da0441652 --- /dev/null +++ b/.changeset/friendly-squids-attack3.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': patch +--- + +passes the `classes` objects down to the `TextField` component from the `Select` and `MultiSelect` components diff --git a/.changeset/friendly-squids-attack4.md b/.changeset/friendly-squids-attack4.md new file mode 100644 index 000000000..a8de2cd45 --- /dev/null +++ b/.changeset/friendly-squids-attack4.md @@ -0,0 +1,5 @@ +--- +'svelte-ux': patch +--- + +fixes a TypeError caused by a faulty ReturnType from `entries()` and removes a related `@ts-expect-error` From 2f7aacb24ffb5f36fd4641a82b8c2eed9cb256d6 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Tue, 2 Jul 2024 13:07:34 -0400 Subject: [PATCH 3/4] Run `pnpm format` --- .../src/lib/components/MultiSelectField.svelte | 2 +- .../svelte-ux/src/lib/components/SelectField.svelte | 2 +- packages/svelte-ux/src/lib/types/typeHelpers.ts | 4 +++- packages/svelte-ux/src/lib/utils/styles.ts | 12 ++++-------- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/svelte-ux/src/lib/components/MultiSelectField.svelte b/packages/svelte-ux/src/lib/components/MultiSelectField.svelte index 764f420bb..d8e439c22 100644 --- a/packages/svelte-ux/src/lib/components/MultiSelectField.svelte +++ b/packages/svelte-ux/src/lib/components/MultiSelectField.svelte @@ -157,7 +157,7 @@ classes={clsMerge( normalizeClasses(settingsClasses.field), { root: 'h-full' }, - normalizeClasses(classes.field), + normalizeClasses(classes.field) )} {...restProps} > diff --git a/packages/svelte-ux/src/lib/components/SelectField.svelte b/packages/svelte-ux/src/lib/components/SelectField.svelte index 9d27856d2..064dcbce3 100644 --- a/packages/svelte-ux/src/lib/components/SelectField.svelte +++ b/packages/svelte-ux/src/lib/components/SelectField.svelte @@ -435,7 +435,7 @@ ? 'border-none shadow-none hover:shadow-none group-focus-within:shadow-none' : undefined, }, - normalizeClasses(classes.field), + normalizeClasses(classes.field) )} role="combobox" aria-expanded={open ? 'true' : 'false'} diff --git a/packages/svelte-ux/src/lib/types/typeHelpers.ts b/packages/svelte-ux/src/lib/types/typeHelpers.ts index 0872e1729..87967651a 100644 --- a/packages/svelte-ux/src/lib/types/typeHelpers.ts +++ b/packages/svelte-ux/src/lib/types/typeHelpers.ts @@ -41,7 +41,9 @@ export function keys(o: T) { export type ObjectKey = string | number | symbol; // Get entries (array of [key, value] arrays) of object (strongly-typed) -export function entries(o: Record): [`${Extract}`, V][]; +export function entries( + o: Record +): [`${Extract}`, V][]; export function entries(o: Map): [K, V][]; // @ts-expect-error export function entries(o: Record | Map) { diff --git a/packages/svelte-ux/src/lib/utils/styles.ts b/packages/svelte-ux/src/lib/utils/styles.ts index 7972ef8ca..f48f623b1 100644 --- a/packages/svelte-ux/src/lib/utils/styles.ts +++ b/packages/svelte-ux/src/lib/utils/styles.ts @@ -42,19 +42,15 @@ const twMerge = extendTailwindMerge({ type ClassFalsyValues = undefined | null | false; type AnyClassValue = ClassValue | ClassFalsyValues; -type AnyClassCollection = - | Record - | ClassFalsyValues; +type AnyClassCollection = Record | ClassFalsyValues; export const cls = (...inputs: AnyClassValue[]) => twMerge(clsx(...inputs)); export const clsMerge = ( ...inputs: T[] ): Exclude => - mergeWith({}, ...inputs.filter(Boolean), (a: string, b: string) => - twMerge(a, b) - ); + mergeWith({}, ...inputs.filter(Boolean), (a: string, b: string) => twMerge(a, b)); export const normalizeClasses = (classes: string | ClassFalsyValues | T): T => { - return classes && typeof classes === 'object' ? classes : { root: classes } as T; -} + return classes && typeof classes === 'object' ? classes : ({ root: classes } as T); +}; From 90f9c8f4cbb538347810b8d8ca5be11dfad2d6a9 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Tue, 2 Jul 2024 14:01:18 -0400 Subject: [PATCH 4/4] Simplify changesets (remove examples) --- .changeset/friendly-squids-attack1.md | 2 +- .changeset/friendly-squids-attack2.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/friendly-squids-attack1.md b/.changeset/friendly-squids-attack1.md index 0294917df..3db832f89 100644 --- a/.changeset/friendly-squids-attack1.md +++ b/.changeset/friendly-squids-attack1.md @@ -2,4 +2,4 @@ 'svelte-ux': patch --- -adds a function `normalizeClasses` to normalize classes when they can be a string or an object, essentially converting strings to objects (e.g. `'p-2'` ➞ `{ root: 'p-2' }`) +adds a function `normalizeClasses` to normalize classes when they can be a string or an object, essentially converting strings to objects diff --git a/.changeset/friendly-squids-attack2.md b/.changeset/friendly-squids-attack2.md index 5143ed437..7eeac38a7 100644 --- a/.changeset/friendly-squids-attack2.md +++ b/.changeset/friendly-squids-attack2.md @@ -2,4 +2,4 @@ 'svelte-ux': patch --- -adds a function `clsMerge` for merging groups of classes without needing to do more complex property-by-property merging (e.g. `{ root: cls(settingsClasses.root, 'grid', classes.root), field: cls(settingsClasses.field, 'flex', classes.field) }` ➞ `clsMerge(settingsClasses, { root: 'grid', field: 'flex' }, classes)`) +adds a function `clsMerge` for merging groups of classes without needing to do more complex property-by-property merging