partial toolbar: basic writable store approach
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
type ToolbarRenderable<P extends unknown[] = any[]> =
|
||||
| { type: 'component'; component: Component; props?: P }
|
||||
| { type: 'snippet'; snippet: Snippet<P>; props?: P }
|
||||
| { type: 'string'; value: string };
|
||||
|
||||
interface ToolbarButton<P extends unknown[] = any[]> {
|
||||
type: 'toggle' | 'button';
|
||||
title: string;
|
||||
render: ToolbarRenderable<P>;
|
||||
}
|
||||
|
||||
interface ToolbarDivider {
|
||||
type: 'divider';
|
||||
}
|
||||
|
||||
interface ToolbarGroup {
|
||||
type: 'group';
|
||||
items: ToolbarItem[];
|
||||
}
|
||||
|
||||
export type ToolbarItem = ToolbarButton<any> | ToolbarDivider | ToolbarGroup;
|
||||
|
||||
type SnippetArgs<T> = T extends Snippet<infer Args extends unknown[]> ? Args : never;
|
||||
|
||||
// For snippet-based buttons
|
||||
export function toolbarItem<S extends Snippet<any>>(item: {
|
||||
type: 'button' | 'toggle';
|
||||
title: string;
|
||||
render: {
|
||||
type: 'snippet';
|
||||
snippet: S;
|
||||
props: SnippetArgs<S>;
|
||||
};
|
||||
}): typeof item;
|
||||
|
||||
// For component-based buttons (optional, for completeness)
|
||||
export function toolbarItem<C extends Component>(item: {
|
||||
type: 'button' | 'toggle';
|
||||
title: string;
|
||||
render: {
|
||||
type: 'component';
|
||||
component: C;
|
||||
props: ComponentProps<C>;
|
||||
};
|
||||
}): typeof item;
|
||||
|
||||
// For other items (divider, group, etc)
|
||||
export function toolbarItem<T extends ToolbarDivider | ToolbarGroup>(item: T): T;
|
||||
|
||||
// Implementation
|
||||
export function toolbarItem(item: any): any {
|
||||
return item;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { type Component, type Snippet, type ComponentProps } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
items: ToolbarItem[];
|
||||
}
|
||||
|
||||
let { items }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#each items as toolbarItem}
|
||||
{@render item(toolbarItem)}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#snippet item(i: ToolbarItem)}
|
||||
{#if i.type === 'button' || i.type === 'toggle'}
|
||||
<button class="toolbar-button">
|
||||
{#if i.render.type === 'component'}
|
||||
<item.render.component {...i.render.props} />
|
||||
{:else if i.render.type === 'snippet'}
|
||||
{@render i.render.snippet(i.render.props)}
|
||||
{:else if i.render.type === 'string'}
|
||||
<span>{i.render.value}</span>
|
||||
{/if}
|
||||
<span>{i.title}</span>
|
||||
</button>
|
||||
{:else if i.type === 'divider'}
|
||||
<hr class="toolbar-divider" />
|
||||
{:else if i.type === 'group'}
|
||||
<div class="toolbar-group">
|
||||
{#each i.items as groupItem}
|
||||
{@render item(groupItem)}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
78
src/lib/Toolbar.svelte.ts
Normal file
78
src/lib/Toolbar.svelte.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -23,4 +23,4 @@ export { default as TimeInput } from './TimeInput.svelte';
|
||||
export { default as TimezoneInput } from './TimezoneInput.svelte';
|
||||
export { default as ToggleGroup } from './ToggleGroup.svelte';
|
||||
export { default as ToggleSelect } from './ToggleSelect.svelte';
|
||||
export { type Option, getLabel, getValue } from './util';
|
||||
export { type Option, type MaybeGetter, getLabel, getValue } from './util';
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* MaybeGetter is a type that can either be a value of type T or a function that returns a value of type T.
|
||||
* This is useful for cases where you might want to pass a value directly or a function that computes the
|
||||
* value later, potentially taking advantage of reactivity.
|
||||
*/
|
||||
export type MaybeGetter<T> = T | (() => T);
|
||||
|
||||
/**
|
||||
* Generates a unique identifier string unless an identifier is provided.
|
||||
* If a prefix is provided, it will be prepended to the identifier.
|
||||
|
||||
Reference in New Issue
Block a user