From 3f4f021fef8a289d8fa15738efbdb4cdad38437a Mon Sep 17 00:00:00 2001 From: Elijah Duffy Date: Wed, 9 Jul 2025 16:46:16 -0700 Subject: [PATCH] add toolbar builder --- src/lib/Toolbar.svelte.ts | 78 -------------------------- src/lib/Toolbar.ts | 114 ++++++++++++++++++++++++++++++++++++++ src/lib/index.ts | 6 ++ src/routes/+page.svelte | 30 +++------- 4 files changed, 127 insertions(+), 101 deletions(-) delete mode 100644 src/lib/Toolbar.svelte.ts create mode 100644 src/lib/Toolbar.ts diff --git a/src/lib/Toolbar.svelte.ts b/src/lib/Toolbar.svelte.ts deleted file mode 100644 index 6c1a695..0000000 --- a/src/lib/Toolbar.svelte.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { writable, type Writable } from 'svelte/store'; - -export type ToolbarToggleState = 'on' | 'off'; - -export type ToolbarToggleOptions = { - state?: Writable; - group?: string; -}; - -export class Toolbar { - private _groups: string[] = []; - - constructor() {} - - group(name: string) { - if (this._groups.includes(name)) { - throw new Error(`Toolbar group "${name}" already exists.`); - } - this._groups.push(name); - - return { - ['data-sui-toolbar-group']: name - }; - } - - toggle(opts: ToolbarToggleOptions = {}) { - if (opts.group && !this._groups.includes(opts.group)) { - throw new Error(`Toolbar group "${opts.group}" does not exist.`); - } - - const toggleState = opts.state ?? writable('off'); - - const bindDataState = (node: HTMLElement) => { - const unsubscribe = toggleState.subscribe((value) => { - console.log('state changed', value); - node.dataset.state = value; - }); - return { destroy: unsubscribe }; - }; - - return { - ['data-sui-toolbar-toggle']: opts?.group || '', - onclick: (event: MouseEvent) => { - const target = event.currentTarget as HTMLElement; - if (!target) return; - - // Toggle data-state - // const currentState = target.dataset.state || 'off'; - // if (opts?.state) { - // opts.state = currentState === 'on' ? 'off' : 'on'; - // } else { - // target.dataset.state = currentState === 'on' ? 'off' : 'on'; - // } - - toggleState.update((current) => (current === 'on' ? 'off' : 'on')); - - // opts.state = opts.state === 'on' ? 'off' : 'on'; - - // If this is a member of a group, find the adjacent toggles and turn them off - if (opts?.group) { - const groupToggles = document.querySelectorAll( - `[data-sui-toolbar-toggle="${opts.group}"]` - ) as NodeListOf; - - groupToggles.forEach((toggle) => { - if (toggle !== target) { - toggle.dataset.state = 'off'; - } - }); - } - - console.log('onlick', opts.group, toggleState); - }, - bindDataState, // expose data-state attribute binder - state: toggleState // expose state to consumer - }; - } -} diff --git a/src/lib/Toolbar.ts b/src/lib/Toolbar.ts new file mode 100644 index 0000000..e7ffada --- /dev/null +++ b/src/lib/Toolbar.ts @@ -0,0 +1,114 @@ +import { createAttachmentKey, type Attachment } from 'svelte/attachments'; +import { writable, type Writable } from 'svelte/store'; + +export type ToolbarToggleState = 'on' | 'off'; + +/** + * Defines options that may be passed when creating a toggleable toolbar item. + */ +export type ToolbarToggleOptions = { + state?: Writable; +}; + +/** toggleValue swaps a toolbar toggle state to on or off, based on current state. */ +const toggleValue = (current: ToolbarToggleState): ToolbarToggleState => + current === 'on' ? 'off' : 'on'; + +/** createGenericToggle creates a toggle. If a list of toggles is passed, the toggle is created as a group item */ +const createGenericToggle = ( + toggles: Writable[] | null, + opts: ToolbarToggleOptions = {} +) => { + const toggleState = opts.state ?? writable('off'); + // If this is a group, add this toggle to it + if (toggles !== null) toggles.push(toggleState); + + return { + state: toggleState, + [createAttachmentKey()]: ((node) => { + const el = node as HTMLElement; + // Subscribe to store + const unsubscribe = toggleState.subscribe((state) => { + el.dataset.state = state; + }); + + return unsubscribe; + }) as Attachment, + onclick: (e: MouseEvent) => { + const target = e.currentTarget as HTMLElement; + if (!target) return; + + toggleState.update(toggleValue); + + // If this is a group, turn off all other toggles + if (toggles !== null) { + toggles.forEach((toggle) => { + if (toggle !== toggleState) { + toggle.update(() => 'off'); + } + }); + } + } + }; +}; + +/** + * A group of toggles in a toolbar. Makes each toggle exclusive of the others, disabling all other + * toggles when any given toggle is enabled. + */ +export class ToolbarGroup { + private _name: string; + private _toggles: Writable[] = []; + + constructor(name: string) { + this._name = name; + } + + /** + * The name of the toolbar group. + */ + get name() { + return this._name; + } + + /** + * Creates a new toolbar toogle item in the group. + * @param opts + * @returns object containing state and properties + */ + toggle(opts: ToolbarToggleOptions = {}) { + return createGenericToggle(this._toggles, opts); + } +} + +/** + * Manages the top-level toolbar groups. + */ +export class Toolbar { + private _groups: ToolbarGroup[] = []; + + constructor() {} + + /** + * Creates a new toolbar group. + * @param name The name of the group. + * @returns The newly created toolbar group. + */ + group(name: string) { + if (this._groups.some((g) => g.name === name)) { + throw new Error(`Toolbar group "${name}" already exists.`); + } + const g = new ToolbarGroup(name); + this._groups.push(g); + return g; + } + + /** + * Creates a new toolbar toggle item. + * @param opts + * @returns object containing state and properties + */ + toggle(opts: ToolbarToggleOptions = {}) { + return createGenericToggle(null, opts); + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts index 33498f3..0dcc51b 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -24,3 +24,9 @@ export { default as TimezoneInput } from './TimezoneInput.svelte'; export { default as ToggleGroup } from './ToggleGroup.svelte'; export { default as ToggleSelect } from './ToggleSelect.svelte'; export { type Option, type MaybeGetter, getLabel, getValue } from './util'; +export { + type ToolbarToggleState, + type ToolbarToggleOptions, + ToolbarGroup, + Toolbar +} from './Toolbar'; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6948e32..ae6c7db 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -17,7 +17,7 @@ import TimezoneInput from '$lib/TimezoneInput.svelte'; import ToggleGroup from '$lib/ToggleGroup.svelte'; import ToggleSelect from '$lib/ToggleSelect.svelte'; - import { Toolbar, type ToolbarToggleState } from '$lib/Toolbar.svelte'; + import { Toolbar } from '$lib/Toolbar'; import { ArrowUUpLeft, ArrowUUpRight, @@ -33,7 +33,7 @@ const toolbar = new Toolbar(); const fontGroup = toolbar.group('font'); - const { state: boldState, ...boldToggle } = toolbar.toggle({ group: 'font' }); + const { state: boldState, ...boldProps } = fontGroup.toggle(); sui @@ -235,37 +235,21 @@
-
- - -