add toolbar builder
This commit is contained in:
@@ -1,78 +0,0 @@
|
|||||||
import { writable, type Writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export type ToolbarToggleState = 'on' | 'off';
|
|
||||||
|
|
||||||
export type ToolbarToggleOptions = {
|
|
||||||
state?: Writable<ToolbarToggleState>;
|
|
||||||
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<ToolbarToggleState>('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<HTMLElement>;
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
114
src/lib/Toolbar.ts
Normal file
114
src/lib/Toolbar.ts
Normal file
@@ -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<ToolbarToggleState>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 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<ToolbarToggleState>[] | null,
|
||||||
|
opts: ToolbarToggleOptions = {}
|
||||||
|
) => {
|
||||||
|
const toggleState = opts.state ?? writable<ToolbarToggleState>('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<ToolbarToggleState>[] = [];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,3 +24,9 @@ export { default as TimezoneInput } from './TimezoneInput.svelte';
|
|||||||
export { default as ToggleGroup } from './ToggleGroup.svelte';
|
export { default as ToggleGroup } from './ToggleGroup.svelte';
|
||||||
export { default as ToggleSelect } from './ToggleSelect.svelte';
|
export { default as ToggleSelect } from './ToggleSelect.svelte';
|
||||||
export { type Option, type MaybeGetter, getLabel, getValue } from './util';
|
export { type Option, type MaybeGetter, getLabel, getValue } from './util';
|
||||||
|
export {
|
||||||
|
type ToolbarToggleState,
|
||||||
|
type ToolbarToggleOptions,
|
||||||
|
ToolbarGroup,
|
||||||
|
Toolbar
|
||||||
|
} from './Toolbar';
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import TimezoneInput from '$lib/TimezoneInput.svelte';
|
import TimezoneInput from '$lib/TimezoneInput.svelte';
|
||||||
import ToggleGroup from '$lib/ToggleGroup.svelte';
|
import ToggleGroup from '$lib/ToggleGroup.svelte';
|
||||||
import ToggleSelect from '$lib/ToggleSelect.svelte';
|
import ToggleSelect from '$lib/ToggleSelect.svelte';
|
||||||
import { Toolbar, type ToolbarToggleState } from '$lib/Toolbar.svelte';
|
import { Toolbar } from '$lib/Toolbar';
|
||||||
import {
|
import {
|
||||||
ArrowUUpLeft,
|
ArrowUUpLeft,
|
||||||
ArrowUUpRight,
|
ArrowUUpRight,
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
const toolbar = new Toolbar();
|
const toolbar = new Toolbar();
|
||||||
const fontGroup = toolbar.group('font');
|
const fontGroup = toolbar.group('font');
|
||||||
const { state: boldState, ...boldToggle } = toolbar.toggle({ group: 'font' });
|
const { state: boldState, ...boldProps } = fontGroup.toggle();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<title>sui</title>
|
<title>sui</title>
|
||||||
@@ -235,37 +235,21 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-sui-text/50 w-[1px] self-stretch"></div>
|
<div class="bg-sui-text/50 w-[1px] self-stretch"></div>
|
||||||
<div class="flex items-center gap-1" {...fontGroup}>
|
<div class="flex items-center gap-1">
|
||||||
<button
|
<button class="item" title="Toggle Bold" aria-label="bold" {...boldProps}>
|
||||||
class="item"
|
|
||||||
title="Toggle Bold"
|
|
||||||
aria-label="bold"
|
|
||||||
{...boldToggle}
|
|
||||||
use:boldToggle.bindDataState
|
|
||||||
>
|
|
||||||
<TextB size="1.25em" />
|
<TextB size="1.25em" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="item" title="Toggle Italic" aria-label="italic" {...fontGroup.toggle()}>
|
||||||
class="item"
|
|
||||||
title="Toggle Italic"
|
|
||||||
aria-label="italic"
|
|
||||||
{...toolbar.toggle({ group: 'font' })}
|
|
||||||
>
|
|
||||||
<TextItalic size="1.25em" />
|
<TextItalic size="1.25em" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="item" title="Toggle Underline" aria-label="underline" {...fontGroup.toggle()}>
|
||||||
class="item"
|
|
||||||
title="Toggle Underline"
|
|
||||||
aria-label="underline"
|
|
||||||
{...toolbar.toggle({ group: 'font' })}
|
|
||||||
>
|
|
||||||
<TextUnderline size="1.25em" />
|
<TextUnderline size="1.25em" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="item"
|
class="item"
|
||||||
title="Toggle Strikethrough"
|
title="Toggle Strikethrough"
|
||||||
aria-label="strikethrough"
|
aria-label="strikethrough"
|
||||||
{...toolbar.toggle({ group: 'font' })}
|
{...fontGroup.toggle()}
|
||||||
>
|
>
|
||||||
<TextStrikethrough size="1.25em" />
|
<TextStrikethrough size="1.25em" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user