@llui/components 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/components/accordion.d.ts +115 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +138 -0
- package/dist/components/alert-dialog.d.ts +45 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.js +12 -0
- package/dist/components/angle-slider.d.ts +121 -0
- package/dist/components/angle-slider.d.ts.map +1 -0
- package/dist/components/angle-slider.js +145 -0
- package/dist/components/async-list.d.ts +104 -0
- package/dist/components/async-list.d.ts.map +1 -0
- package/dist/components/async-list.js +117 -0
- package/dist/components/avatar.d.ts +58 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +43 -0
- package/dist/components/carousel.d.ts +128 -0
- package/dist/components/carousel.d.ts.map +1 -0
- package/dist/components/carousel.js +131 -0
- package/dist/components/cascade-select.d.ts +95 -0
- package/dist/components/cascade-select.d.ts.map +1 -0
- package/dist/components/cascade-select.js +100 -0
- package/dist/components/checkbox.d.ts +74 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +73 -0
- package/dist/components/clipboard.d.ts +72 -0
- package/dist/components/clipboard.d.ts.map +1 -0
- package/dist/components/clipboard.js +73 -0
- package/dist/components/collapsible.d.ts +64 -0
- package/dist/components/collapsible.d.ts.map +1 -0
- package/dist/components/collapsible.js +51 -0
- package/dist/components/color-picker.d.ts +125 -0
- package/dist/components/color-picker.d.ts.map +1 -0
- package/dist/components/color-picker.js +169 -0
- package/dist/components/combobox.d.ts +163 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.js +345 -0
- package/dist/components/context-menu.d.ts +105 -0
- package/dist/components/context-menu.d.ts.map +1 -0
- package/dist/components/context-menu.js +177 -0
- package/dist/components/date-input.d.ts +117 -0
- package/dist/components/date-input.d.ts.map +1 -0
- package/dist/components/date-input.js +149 -0
- package/dist/components/date-picker.d.ts +142 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +294 -0
- package/dist/components/dialog.d.ts +152 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +140 -0
- package/dist/components/drawer.d.ts +106 -0
- package/dist/components/drawer.d.ts.map +1 -0
- package/dist/components/drawer.js +136 -0
- package/dist/components/editable.d.ts +92 -0
- package/dist/components/editable.d.ts.map +1 -0
- package/dist/components/editable.js +112 -0
- package/dist/components/file-upload.d.ts +251 -0
- package/dist/components/file-upload.d.ts.map +1 -0
- package/dist/components/file-upload.js +324 -0
- package/dist/components/floating-panel.d.ts +171 -0
- package/dist/components/floating-panel.d.ts.map +1 -0
- package/dist/components/floating-panel.js +198 -0
- package/dist/components/hover-card.d.ts +85 -0
- package/dist/components/hover-card.d.ts.map +1 -0
- package/dist/components/hover-card.js +128 -0
- package/dist/components/image-cropper.d.ts +129 -0
- package/dist/components/image-cropper.d.ts.map +1 -0
- package/dist/components/image-cropper.js +208 -0
- package/dist/components/index.d.ts +109 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +54 -0
- package/dist/components/listbox.d.ts +98 -0
- package/dist/components/listbox.d.ts.map +1 -0
- package/dist/components/listbox.js +174 -0
- package/dist/components/marquee.d.ts +84 -0
- package/dist/components/marquee.d.ts.map +1 -0
- package/dist/components/marquee.js +73 -0
- package/dist/components/menu.d.ts +131 -0
- package/dist/components/menu.d.ts.map +1 -0
- package/dist/components/menu.js +262 -0
- package/dist/components/navigation-menu.d.ts +111 -0
- package/dist/components/navigation-menu.d.ts.map +1 -0
- package/dist/components/navigation-menu.js +102 -0
- package/dist/components/number-input.d.ts +106 -0
- package/dist/components/number-input.d.ts.map +1 -0
- package/dist/components/number-input.js +178 -0
- package/dist/components/pagination.d.ts +113 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +135 -0
- package/dist/components/password-input.d.ts +64 -0
- package/dist/components/password-input.d.ts.map +1 -0
- package/dist/components/password-input.js +52 -0
- package/dist/components/pin-input.d.ts +89 -0
- package/dist/components/pin-input.d.ts.map +1 -0
- package/dist/components/pin-input.js +139 -0
- package/dist/components/popover.d.ts +116 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +146 -0
- package/dist/components/presence.d.ts +71 -0
- package/dist/components/presence.d.ts.map +1 -0
- package/dist/components/presence.js +57 -0
- package/dist/components/progress.d.ts +74 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +80 -0
- package/dist/components/qr-code.d.ts +114 -0
- package/dist/components/qr-code.d.ts.map +1 -0
- package/dist/components/qr-code.js +108 -0
- package/dist/components/radio-group.d.ts +89 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +161 -0
- package/dist/components/rating-group.d.ts +88 -0
- package/dist/components/rating-group.d.ts.map +1 -0
- package/dist/components/rating-group.js +122 -0
- package/dist/components/scroll-area.d.ts +124 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.js +152 -0
- package/dist/components/select.d.ts +161 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +333 -0
- package/dist/components/signature-pad.d.ts +138 -0
- package/dist/components/signature-pad.d.ts.map +1 -0
- package/dist/components/signature-pad.js +142 -0
- package/dist/components/slider.d.ts +117 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +210 -0
- package/dist/components/splitter.d.ts +87 -0
- package/dist/components/splitter.d.ts.map +1 -0
- package/dist/components/splitter.js +119 -0
- package/dist/components/steps.d.ts +104 -0
- package/dist/components/steps.d.ts.map +1 -0
- package/dist/components/steps.js +133 -0
- package/dist/components/switch.d.ts +66 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +59 -0
- package/dist/components/tabs.d.ts +146 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +244 -0
- package/dist/components/tags-input.d.ts +118 -0
- package/dist/components/tags-input.d.ts.map +1 -0
- package/dist/components/tags-input.js +168 -0
- package/dist/components/time-picker.d.ts +121 -0
- package/dist/components/time-picker.d.ts.map +1 -0
- package/dist/components/time-picker.js +147 -0
- package/dist/components/timer.d.ts +131 -0
- package/dist/components/timer.d.ts.map +1 -0
- package/dist/components/timer.js +117 -0
- package/dist/components/toast.d.ts +119 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/toast.js +102 -0
- package/dist/components/toc.d.ts +119 -0
- package/dist/components/toc.d.ts.map +1 -0
- package/dist/components/toc.js +107 -0
- package/dist/components/toggle-group.d.ts +80 -0
- package/dist/components/toggle-group.d.ts.map +1 -0
- package/dist/components/toggle-group.js +93 -0
- package/dist/components/toggle.d.ts +47 -0
- package/dist/components/toggle.d.ts.map +1 -0
- package/dist/components/toggle.js +41 -0
- package/dist/components/tooltip.d.ts +92 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +147 -0
- package/dist/components/tour.d.ts +145 -0
- package/dist/components/tour.d.ts.map +1 -0
- package/dist/components/tour.js +133 -0
- package/dist/components/tree-view.d.ts +216 -0
- package/dist/components/tree-view.d.ts.map +1 -0
- package/dist/components/tree-view.js +293 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/patterns/confirm-dialog.d.ts +92 -0
- package/dist/patterns/confirm-dialog.d.ts.map +1 -0
- package/dist/patterns/confirm-dialog.js +92 -0
- package/dist/patterns/index.d.ts +3 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +1 -0
- package/dist/utils/anatomy.d.ts +40 -0
- package/dist/utils/anatomy.d.ts.map +1 -0
- package/dist/utils/anatomy.js +41 -0
- package/dist/utils/aria-hidden.d.ts +12 -0
- package/dist/utils/aria-hidden.d.ts.map +1 -0
- package/dist/utils/aria-hidden.js +72 -0
- package/dist/utils/dismissable.d.ts +25 -0
- package/dist/utils/dismissable.d.ts.map +1 -0
- package/dist/utils/dismissable.js +65 -0
- package/dist/utils/dom.d.ts +8 -0
- package/dist/utils/dom.d.ts.map +1 -0
- package/dist/utils/dom.js +21 -0
- package/dist/utils/floating.d.ts +44 -0
- package/dist/utils/floating.d.ts.map +1 -0
- package/dist/utils/floating.js +44 -0
- package/dist/utils/focus-trap.d.ts +18 -0
- package/dist/utils/focus-trap.d.ts.map +1 -0
- package/dist/utils/focus-trap.js +85 -0
- package/dist/utils/focusables.d.ts +6 -0
- package/dist/utils/focusables.d.ts.map +1 -0
- package/dist/utils/focusables.js +65 -0
- package/dist/utils/index.d.ts +18 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/interact-outside.d.ts +26 -0
- package/dist/utils/interact-outside.d.ts.map +1 -0
- package/dist/utils/interact-outside.js +46 -0
- package/dist/utils/remove-scroll.d.ts +8 -0
- package/dist/utils/remove-scroll.d.ts.map +1 -0
- package/dist/utils/remove-scroll.js +37 -0
- package/dist/utils/tree-collection.d.ts +61 -0
- package/dist/utils/tree-collection.d.ts.map +1 -0
- package/dist/utils/tree-collection.js +137 -0
- package/dist/utils/typeahead.d.ts +49 -0
- package/dist/utils/typeahead.d.ts.map +1 -0
- package/dist/utils/typeahead.js +81 -0
- package/package.json +282 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Send } from '@llui/dom';
|
|
2
|
+
/**
|
|
3
|
+
* Timer — counts elapsed time up from zero, or down from a configured
|
|
4
|
+
* target. The machine is pure: it doesn't own the ticking interval.
|
|
5
|
+
* The consumer runs `setInterval(() => send({type:'tick', now: Date.now()}), 100)`
|
|
6
|
+
* (or whatever granularity) while the timer is running, and dispatches
|
|
7
|
+
* `start` / `pause` / `reset` in response to user input.
|
|
8
|
+
*
|
|
9
|
+
* Typical display:
|
|
10
|
+
*
|
|
11
|
+
* const t = timer.connect<State>(s => s.timer, msg => send({type: 'timer', msg}))
|
|
12
|
+
* div({ ...t.root }, [
|
|
13
|
+
* div({ ...t.display }, [text(s => timer.formatMs(timer.display(s.timer), 'mm:ss'))]),
|
|
14
|
+
* button({ ...t.startTrigger }, [text('Start')]),
|
|
15
|
+
* button({ ...t.pauseTrigger }, [text('Pause')]),
|
|
16
|
+
* button({ ...t.resetTrigger }, [text('Reset')]),
|
|
17
|
+
* ])
|
|
18
|
+
*/
|
|
19
|
+
export type Direction = 'up' | 'down';
|
|
20
|
+
export interface TimerState {
|
|
21
|
+
running: boolean;
|
|
22
|
+
direction: Direction;
|
|
23
|
+
/** Target in milliseconds for countdown (0 = no target, runs indefinitely). */
|
|
24
|
+
targetMs: number;
|
|
25
|
+
/** Accumulated elapsed time, excluding the current running interval. */
|
|
26
|
+
elapsedMs: number;
|
|
27
|
+
/** Timestamp when the current running interval started (null when paused). */
|
|
28
|
+
startedAt: number | null;
|
|
29
|
+
}
|
|
30
|
+
export type TimerMsg = {
|
|
31
|
+
type: 'start';
|
|
32
|
+
now: number;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'pause';
|
|
35
|
+
now: number;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'reset';
|
|
38
|
+
} | {
|
|
39
|
+
type: 'tick';
|
|
40
|
+
now: number;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'setTarget';
|
|
43
|
+
targetMs: number;
|
|
44
|
+
};
|
|
45
|
+
export interface TimerInit {
|
|
46
|
+
direction?: Direction;
|
|
47
|
+
targetMs?: number;
|
|
48
|
+
elapsedMs?: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function init(opts?: TimerInit): TimerState;
|
|
51
|
+
export declare function update(state: TimerState, msg: TimerMsg): [TimerState, never[]];
|
|
52
|
+
/** Returns the display value in ms (elapsed for count-up, remaining for count-down). */
|
|
53
|
+
export declare function display(state: TimerState): number;
|
|
54
|
+
export declare function isComplete(state: TimerState): boolean;
|
|
55
|
+
/** Breaks a ms value into `{ hours, minutes, seconds, ms }` parts for rendering. */
|
|
56
|
+
export declare function parts(ms: number): {
|
|
57
|
+
hours: number;
|
|
58
|
+
minutes: number;
|
|
59
|
+
seconds: number;
|
|
60
|
+
ms: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Format a ms value using a simple template. Supported tokens:
|
|
64
|
+
* HH / H — hours (2-digit / unpadded)
|
|
65
|
+
* mm / m — minutes
|
|
66
|
+
* ss / s — seconds
|
|
67
|
+
* SSS / S — milliseconds (3-digit / unpadded)
|
|
68
|
+
*
|
|
69
|
+
* Example: formatMs(125_500, 'mm:ss.SSS') → "02:05.500"
|
|
70
|
+
*/
|
|
71
|
+
export declare function formatMs(ms: number, template: string): string;
|
|
72
|
+
export interface TimerParts<S> {
|
|
73
|
+
root: {
|
|
74
|
+
'data-scope': 'timer';
|
|
75
|
+
'data-part': 'root';
|
|
76
|
+
'data-running': (s: S) => '' | undefined;
|
|
77
|
+
'data-direction': (s: S) => Direction;
|
|
78
|
+
};
|
|
79
|
+
display: {
|
|
80
|
+
role: 'timer';
|
|
81
|
+
'aria-live': 'off' | 'polite';
|
|
82
|
+
'data-scope': 'timer';
|
|
83
|
+
'data-part': 'display';
|
|
84
|
+
};
|
|
85
|
+
startTrigger: {
|
|
86
|
+
type: 'button';
|
|
87
|
+
'aria-label': string;
|
|
88
|
+
'data-scope': 'timer';
|
|
89
|
+
'data-part': 'start-trigger';
|
|
90
|
+
disabled: (s: S) => boolean;
|
|
91
|
+
onClick: (e: MouseEvent) => void;
|
|
92
|
+
};
|
|
93
|
+
pauseTrigger: {
|
|
94
|
+
type: 'button';
|
|
95
|
+
'aria-label': string;
|
|
96
|
+
'data-scope': 'timer';
|
|
97
|
+
'data-part': 'pause-trigger';
|
|
98
|
+
disabled: (s: S) => boolean;
|
|
99
|
+
onClick: (e: MouseEvent) => void;
|
|
100
|
+
};
|
|
101
|
+
resetTrigger: {
|
|
102
|
+
type: 'button';
|
|
103
|
+
'aria-label': string;
|
|
104
|
+
'data-scope': 'timer';
|
|
105
|
+
'data-part': 'reset-trigger';
|
|
106
|
+
onClick: (e: MouseEvent) => void;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export interface ConnectOptions {
|
|
110
|
+
startLabel?: string;
|
|
111
|
+
pauseLabel?: string;
|
|
112
|
+
resetLabel?: string;
|
|
113
|
+
/**
|
|
114
|
+
* aria-live politeness for the display element. `'polite'` announces
|
|
115
|
+
* updates to assistive tech; `'off'` (default) keeps it silent — use
|
|
116
|
+
* 'polite' sparingly to avoid spamming screen reader users with
|
|
117
|
+
* every tick.
|
|
118
|
+
*/
|
|
119
|
+
ariaLive?: 'off' | 'polite';
|
|
120
|
+
}
|
|
121
|
+
export declare function connect<S>(get: (s: S) => TimerState, send: Send<TimerMsg>, opts?: ConnectOptions): TimerParts<S>;
|
|
122
|
+
export declare const timer: {
|
|
123
|
+
init: typeof init;
|
|
124
|
+
update: typeof update;
|
|
125
|
+
connect: typeof connect;
|
|
126
|
+
display: typeof display;
|
|
127
|
+
isComplete: typeof isComplete;
|
|
128
|
+
parts: typeof parts;
|
|
129
|
+
formatMs: typeof formatMs;
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=timer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timer.d.ts","sourceRoot":"","sources":["../../src/components/timer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export function init(opts = {}) {
|
|
2
|
+
return {
|
|
3
|
+
running: false,
|
|
4
|
+
direction: opts.direction ?? 'up',
|
|
5
|
+
targetMs: opts.targetMs ?? 0,
|
|
6
|
+
elapsedMs: opts.elapsedMs ?? 0,
|
|
7
|
+
startedAt: null,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function update(state, msg) {
|
|
11
|
+
switch (msg.type) {
|
|
12
|
+
case 'start':
|
|
13
|
+
if (state.running)
|
|
14
|
+
return [state, []];
|
|
15
|
+
return [{ ...state, running: true, startedAt: msg.now }, []];
|
|
16
|
+
case 'pause': {
|
|
17
|
+
if (!state.running || state.startedAt === null)
|
|
18
|
+
return [state, []];
|
|
19
|
+
const elapsed = state.elapsedMs + (msg.now - state.startedAt);
|
|
20
|
+
return [{ ...state, running: false, elapsedMs: elapsed, startedAt: null }, []];
|
|
21
|
+
}
|
|
22
|
+
case 'reset':
|
|
23
|
+
return [{ ...state, running: false, elapsedMs: 0, startedAt: null }, []];
|
|
24
|
+
case 'tick': {
|
|
25
|
+
if (!state.running || state.startedAt === null)
|
|
26
|
+
return [state, []];
|
|
27
|
+
const elapsed = state.elapsedMs + (msg.now - state.startedAt);
|
|
28
|
+
// Countdown: auto-stop at target.
|
|
29
|
+
if (state.direction === 'down' && state.targetMs > 0 && elapsed >= state.targetMs) {
|
|
30
|
+
return [{ ...state, running: false, elapsedMs: state.targetMs, startedAt: null }, []];
|
|
31
|
+
}
|
|
32
|
+
return [{ ...state, elapsedMs: elapsed, startedAt: msg.now }, []];
|
|
33
|
+
}
|
|
34
|
+
case 'setTarget':
|
|
35
|
+
return [{ ...state, targetMs: msg.targetMs }, []];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Returns the display value in ms (elapsed for count-up, remaining for count-down). */
|
|
39
|
+
export function display(state) {
|
|
40
|
+
if (state.direction === 'up')
|
|
41
|
+
return state.elapsedMs;
|
|
42
|
+
return Math.max(0, state.targetMs - state.elapsedMs);
|
|
43
|
+
}
|
|
44
|
+
export function isComplete(state) {
|
|
45
|
+
return state.direction === 'down' && state.targetMs > 0 && state.elapsedMs >= state.targetMs;
|
|
46
|
+
}
|
|
47
|
+
/** Breaks a ms value into `{ hours, minutes, seconds, ms }` parts for rendering. */
|
|
48
|
+
export function parts(ms) {
|
|
49
|
+
const total = Math.max(0, Math.floor(ms));
|
|
50
|
+
const hours = Math.floor(total / 3_600_000);
|
|
51
|
+
const minutes = Math.floor((total % 3_600_000) / 60_000);
|
|
52
|
+
const seconds = Math.floor((total % 60_000) / 1000);
|
|
53
|
+
const rem = total % 1000;
|
|
54
|
+
return { hours, minutes, seconds, ms: rem };
|
|
55
|
+
}
|
|
56
|
+
const pad = (n, width) => String(n).padStart(width, '0');
|
|
57
|
+
/**
|
|
58
|
+
* Format a ms value using a simple template. Supported tokens:
|
|
59
|
+
* HH / H — hours (2-digit / unpadded)
|
|
60
|
+
* mm / m — minutes
|
|
61
|
+
* ss / s — seconds
|
|
62
|
+
* SSS / S — milliseconds (3-digit / unpadded)
|
|
63
|
+
*
|
|
64
|
+
* Example: formatMs(125_500, 'mm:ss.SSS') → "02:05.500"
|
|
65
|
+
*/
|
|
66
|
+
export function formatMs(ms, template) {
|
|
67
|
+
const p = parts(ms);
|
|
68
|
+
return template
|
|
69
|
+
.replace(/HH/g, pad(p.hours, 2))
|
|
70
|
+
.replace(/H/g, String(p.hours))
|
|
71
|
+
.replace(/mm/g, pad(p.minutes, 2))
|
|
72
|
+
.replace(/m/g, String(p.minutes))
|
|
73
|
+
.replace(/ss/g, pad(p.seconds, 2))
|
|
74
|
+
.replace(/s/g, String(p.seconds))
|
|
75
|
+
.replace(/SSS/g, pad(p.ms, 3))
|
|
76
|
+
.replace(/S/g, String(p.ms));
|
|
77
|
+
}
|
|
78
|
+
export function connect(get, send, opts = {}) {
|
|
79
|
+
return {
|
|
80
|
+
root: {
|
|
81
|
+
'data-scope': 'timer',
|
|
82
|
+
'data-part': 'root',
|
|
83
|
+
'data-running': (s) => (get(s).running ? '' : undefined),
|
|
84
|
+
'data-direction': (s) => get(s).direction,
|
|
85
|
+
},
|
|
86
|
+
display: {
|
|
87
|
+
role: 'timer',
|
|
88
|
+
'aria-live': opts.ariaLive ?? 'off',
|
|
89
|
+
'data-scope': 'timer',
|
|
90
|
+
'data-part': 'display',
|
|
91
|
+
},
|
|
92
|
+
startTrigger: {
|
|
93
|
+
type: 'button',
|
|
94
|
+
'aria-label': opts.startLabel ?? 'Start timer',
|
|
95
|
+
'data-scope': 'timer',
|
|
96
|
+
'data-part': 'start-trigger',
|
|
97
|
+
disabled: (s) => get(s).running,
|
|
98
|
+
onClick: () => send({ type: 'start', now: Date.now() }),
|
|
99
|
+
},
|
|
100
|
+
pauseTrigger: {
|
|
101
|
+
type: 'button',
|
|
102
|
+
'aria-label': opts.pauseLabel ?? 'Pause timer',
|
|
103
|
+
'data-scope': 'timer',
|
|
104
|
+
'data-part': 'pause-trigger',
|
|
105
|
+
disabled: (s) => !get(s).running,
|
|
106
|
+
onClick: () => send({ type: 'pause', now: Date.now() }),
|
|
107
|
+
},
|
|
108
|
+
resetTrigger: {
|
|
109
|
+
type: 'button',
|
|
110
|
+
'aria-label': opts.resetLabel ?? 'Reset timer',
|
|
111
|
+
'data-scope': 'timer',
|
|
112
|
+
'data-part': 'reset-trigger',
|
|
113
|
+
onClick: () => send({ type: 'reset' }),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export const timer = { init, update, connect, display, isComplete, parts, formatMs };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Send } from '@llui/dom';
|
|
2
|
+
/**
|
|
3
|
+
* Toast — ephemeral non-modal notifications rendered in a fixed region.
|
|
4
|
+
* Multiple toasts can be active at once. Each has a duration after which
|
|
5
|
+
* it auto-dismisses (unless paused or persistent).
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - `toast.toaster` state manages a collection of toasts.
|
|
9
|
+
* - Duration countdown is handled externally — the consumer schedules
|
|
10
|
+
* a `dismiss` message via setTimeout (or uses the `scheduleDismiss`
|
|
11
|
+
* effect-style helper in your onEffect handler).
|
|
12
|
+
*/
|
|
13
|
+
export type ToastType = 'info' | 'success' | 'warning' | 'error' | 'loading' | 'custom';
|
|
14
|
+
export type ToastPlacement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end';
|
|
15
|
+
export interface Toast {
|
|
16
|
+
id: string;
|
|
17
|
+
type: ToastType;
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
/** ms until auto-dismiss. Use Infinity for persistent. */
|
|
21
|
+
duration: number;
|
|
22
|
+
/** Whether the toast can be manually dismissed. */
|
|
23
|
+
dismissable: boolean;
|
|
24
|
+
/** Optional pause flag — consumer sets while user hovers. */
|
|
25
|
+
paused: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface ToasterState {
|
|
28
|
+
toasts: Toast[];
|
|
29
|
+
max: number;
|
|
30
|
+
placement: ToastPlacement;
|
|
31
|
+
}
|
|
32
|
+
export type ToasterMsg = {
|
|
33
|
+
type: 'create';
|
|
34
|
+
toast: Omit<Toast, 'paused'> & {
|
|
35
|
+
paused?: boolean;
|
|
36
|
+
};
|
|
37
|
+
} | {
|
|
38
|
+
type: 'dismiss';
|
|
39
|
+
id: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'dismissAll';
|
|
42
|
+
} | {
|
|
43
|
+
type: 'update';
|
|
44
|
+
id: string;
|
|
45
|
+
patch: Partial<Toast>;
|
|
46
|
+
} | {
|
|
47
|
+
type: 'pause';
|
|
48
|
+
id: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'resume';
|
|
51
|
+
id: string;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'pauseAll';
|
|
54
|
+
} | {
|
|
55
|
+
type: 'resumeAll';
|
|
56
|
+
};
|
|
57
|
+
export interface ToasterInit {
|
|
58
|
+
max?: number;
|
|
59
|
+
placement?: ToastPlacement;
|
|
60
|
+
}
|
|
61
|
+
export declare function init(opts?: ToasterInit): ToasterState;
|
|
62
|
+
export declare function update(state: ToasterState, msg: ToasterMsg): [ToasterState, never[]];
|
|
63
|
+
export declare function nextToastId(): string;
|
|
64
|
+
export interface ToastItemParts<_S> {
|
|
65
|
+
root: {
|
|
66
|
+
role: 'status';
|
|
67
|
+
'aria-atomic': 'true';
|
|
68
|
+
'aria-live': 'polite' | 'assertive';
|
|
69
|
+
id: string;
|
|
70
|
+
'data-scope': 'toast';
|
|
71
|
+
'data-part': 'root';
|
|
72
|
+
'data-type': ToastType;
|
|
73
|
+
'data-id': string;
|
|
74
|
+
onPointerEnter: (e: PointerEvent) => void;
|
|
75
|
+
onPointerLeave: (e: PointerEvent) => void;
|
|
76
|
+
onFocus: (e: FocusEvent) => void;
|
|
77
|
+
onBlur: (e: FocusEvent) => void;
|
|
78
|
+
};
|
|
79
|
+
title: {
|
|
80
|
+
id: string;
|
|
81
|
+
'data-scope': 'toast';
|
|
82
|
+
'data-part': 'title';
|
|
83
|
+
};
|
|
84
|
+
description: {
|
|
85
|
+
id: string;
|
|
86
|
+
'data-scope': 'toast';
|
|
87
|
+
'data-part': 'description';
|
|
88
|
+
};
|
|
89
|
+
closeTrigger: {
|
|
90
|
+
type: 'button';
|
|
91
|
+
'aria-label': string;
|
|
92
|
+
'data-scope': 'toast';
|
|
93
|
+
'data-part': 'close-trigger';
|
|
94
|
+
onClick: (e: MouseEvent) => void;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export interface ToasterParts<S> {
|
|
98
|
+
region: {
|
|
99
|
+
role: 'region';
|
|
100
|
+
'aria-label': string;
|
|
101
|
+
tabIndex: -1;
|
|
102
|
+
'data-scope': 'toast';
|
|
103
|
+
'data-part': 'region';
|
|
104
|
+
'data-placement': (s: S) => ToastPlacement;
|
|
105
|
+
};
|
|
106
|
+
toast: (toast: Toast) => ToastItemParts<S>;
|
|
107
|
+
}
|
|
108
|
+
export interface ConnectOptions {
|
|
109
|
+
regionLabel?: string;
|
|
110
|
+
closeLabel?: string;
|
|
111
|
+
}
|
|
112
|
+
export declare function connect<S>(_get: (s: S) => ToasterState, send: Send<ToasterMsg>, opts?: ConnectOptions): ToasterParts<S>;
|
|
113
|
+
export declare const toast: {
|
|
114
|
+
init: typeof init;
|
|
115
|
+
update: typeof update;
|
|
116
|
+
connect: typeof connect;
|
|
117
|
+
nextToastId: typeof nextToastId;
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=toast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../src/components/toast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAA;AACvF,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,WAAW,GACX,SAAS,GACT,QAAQ,GACR,cAAc,GACd,YAAY,CAAA;AAEhB,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAA;IAChB,mDAAmD;IACnD,WAAW,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,cAAc,CAAA;CAC1B;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAA;AAEzB,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,cAAc,CAAA;CAC3B;AAED,wBAAgB,IAAI,CAAC,IAAI,GAAE,WAAgB,GAAG,YAAY,CAMzD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC,CA0CpF;AAGD,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,MAAM,WAAW,cAAc,CAAC,EAAE;IAChC,IAAI,EAAE;QACJ,IAAI,EAAE,QAAQ,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;QACrB,WAAW,EAAE,QAAQ,GAAG,WAAW,CAAA;QACnC,EAAE,EAAE,MAAM,CAAA;QACV,YAAY,EAAE,OAAO,CAAA;QACrB,WAAW,EAAE,MAAM,CAAA;QACnB,WAAW,EAAE,SAAS,CAAA;QACtB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAA;QACzC,cAAc,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAA;QACzC,OAAO,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;QAChC,MAAM,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;KAChC,CAAA;IACD,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAA;QACV,YAAY,EAAE,OAAO,CAAA;QACrB,WAAW,EAAE,OAAO,CAAA;KACrB,CAAA;IACD,WAAW,EAAE;QACX,EAAE,EAAE,MAAM,CAAA;QACV,YAAY,EAAE,OAAO,CAAA;QACrB,WAAW,EAAE,aAAa,CAAA;KAC3B,CAAA;IACD,YAAY,EAAE;QACZ,IAAI,EAAE,QAAQ,CAAA;QACd,YAAY,EAAE,MAAM,CAAA;QACpB,YAAY,EAAE,OAAO,CAAA;QACrB,WAAW,EAAE,eAAe,CAAA;QAC5B,OAAO,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;KACjC,CAAA;CACF;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ,CAAA;QACd,YAAY,EAAE,MAAM,CAAA;QACpB,QAAQ,EAAE,CAAC,CAAC,CAAA;QACZ,YAAY,EAAE,OAAO,CAAA;QACrB,WAAW,EAAE,QAAQ,CAAA;QACrB,gBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,cAAc,CAAA;KAC3C,CAAA;IACD,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,cAAc,CAAC,CAAC,CAAC,CAAA;CAC3C;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,OAAO,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,YAAY,EAC5B,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,EACtB,IAAI,GAAE,cAAmB,GACxB,YAAY,CAAC,CAAC,CAAC,CA+CjB;AAED,eAAO,MAAM,KAAK;;;;;CAAyC,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export function init(opts = {}) {
|
|
2
|
+
return {
|
|
3
|
+
toasts: [],
|
|
4
|
+
max: opts.max ?? 5,
|
|
5
|
+
placement: opts.placement ?? 'bottom-end',
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function update(state, msg) {
|
|
9
|
+
switch (msg.type) {
|
|
10
|
+
case 'create': {
|
|
11
|
+
const toast = { paused: false, ...msg.toast };
|
|
12
|
+
const toasts = [...state.toasts, toast];
|
|
13
|
+
// Enforce max — drop oldest
|
|
14
|
+
const trimmed = toasts.length > state.max ? toasts.slice(-state.max) : toasts;
|
|
15
|
+
return [{ ...state, toasts: trimmed }, []];
|
|
16
|
+
}
|
|
17
|
+
case 'dismiss':
|
|
18
|
+
return [{ ...state, toasts: state.toasts.filter((t) => t.id !== msg.id) }, []];
|
|
19
|
+
case 'dismissAll':
|
|
20
|
+
return [{ ...state, toasts: [] }, []];
|
|
21
|
+
case 'update':
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
...state,
|
|
25
|
+
toasts: state.toasts.map((t) => (t.id === msg.id ? { ...t, ...msg.patch } : t)),
|
|
26
|
+
},
|
|
27
|
+
[],
|
|
28
|
+
];
|
|
29
|
+
case 'pause':
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
...state,
|
|
33
|
+
toasts: state.toasts.map((t) => (t.id === msg.id ? { ...t, paused: true } : t)),
|
|
34
|
+
},
|
|
35
|
+
[],
|
|
36
|
+
];
|
|
37
|
+
case 'resume':
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
...state,
|
|
41
|
+
toasts: state.toasts.map((t) => (t.id === msg.id ? { ...t, paused: false } : t)),
|
|
42
|
+
},
|
|
43
|
+
[],
|
|
44
|
+
];
|
|
45
|
+
case 'pauseAll':
|
|
46
|
+
return [{ ...state, toasts: state.toasts.map((t) => ({ ...t, paused: true })) }, []];
|
|
47
|
+
case 'resumeAll':
|
|
48
|
+
return [{ ...state, toasts: state.toasts.map((t) => ({ ...t, paused: false })) }, []];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
let toastIdCounter = 0;
|
|
52
|
+
export function nextToastId() {
|
|
53
|
+
return `toast-${++toastIdCounter}`;
|
|
54
|
+
}
|
|
55
|
+
export function connect(_get, send, opts = {}) {
|
|
56
|
+
const regionLabel = opts.regionLabel ?? 'Notifications';
|
|
57
|
+
const closeLabel = opts.closeLabel ?? 'Dismiss notification';
|
|
58
|
+
return {
|
|
59
|
+
region: {
|
|
60
|
+
role: 'region',
|
|
61
|
+
'aria-label': regionLabel,
|
|
62
|
+
tabIndex: -1,
|
|
63
|
+
'data-scope': 'toast',
|
|
64
|
+
'data-part': 'region',
|
|
65
|
+
'data-placement': (s) => _get(s).placement,
|
|
66
|
+
},
|
|
67
|
+
toast: (toast) => ({
|
|
68
|
+
root: {
|
|
69
|
+
role: 'status',
|
|
70
|
+
'aria-atomic': 'true',
|
|
71
|
+
'aria-live': toast.type === 'error' ? 'assertive' : 'polite',
|
|
72
|
+
id: `${toast.id}:root`,
|
|
73
|
+
'data-scope': 'toast',
|
|
74
|
+
'data-part': 'root',
|
|
75
|
+
'data-type': toast.type,
|
|
76
|
+
'data-id': toast.id,
|
|
77
|
+
onPointerEnter: () => send({ type: 'pause', id: toast.id }),
|
|
78
|
+
onPointerLeave: () => send({ type: 'resume', id: toast.id }),
|
|
79
|
+
onFocus: () => send({ type: 'pause', id: toast.id }),
|
|
80
|
+
onBlur: () => send({ type: 'resume', id: toast.id }),
|
|
81
|
+
},
|
|
82
|
+
title: {
|
|
83
|
+
id: `${toast.id}:title`,
|
|
84
|
+
'data-scope': 'toast',
|
|
85
|
+
'data-part': 'title',
|
|
86
|
+
},
|
|
87
|
+
description: {
|
|
88
|
+
id: `${toast.id}:description`,
|
|
89
|
+
'data-scope': 'toast',
|
|
90
|
+
'data-part': 'description',
|
|
91
|
+
},
|
|
92
|
+
closeTrigger: {
|
|
93
|
+
type: 'button',
|
|
94
|
+
'aria-label': closeLabel,
|
|
95
|
+
'data-scope': 'toast',
|
|
96
|
+
'data-part': 'close-trigger',
|
|
97
|
+
onClick: () => send({ type: 'dismiss', id: toast.id }),
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export const toast = { init, update, connect, nextToastId };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Send } from '@llui/dom';
|
|
2
|
+
/**
|
|
3
|
+
* Table of contents — a navigation list that tracks which heading is
|
|
4
|
+
* currently visible in the main scroll area and highlights it. The
|
|
5
|
+
* state machine tracks the flat list of heading ids and the currently
|
|
6
|
+
* active one; the view layer installs an IntersectionObserver in
|
|
7
|
+
* onMount to detect which heading is on screen and dispatches
|
|
8
|
+
* `setActive`.
|
|
9
|
+
*
|
|
10
|
+
* Typical setup in onMount:
|
|
11
|
+
*
|
|
12
|
+
* const headings = document.querySelectorAll('h2[id], h3[id]')
|
|
13
|
+
* const io = new IntersectionObserver((entries) => {
|
|
14
|
+
* for (const e of entries) {
|
|
15
|
+
* if (e.isIntersecting) send({ type: 'setActive', id: e.target.id })
|
|
16
|
+
* }
|
|
17
|
+
* }, { rootMargin: '0px 0px -80% 0px' })
|
|
18
|
+
* headings.forEach((h) => io.observe(h))
|
|
19
|
+
* return () => io.disconnect()
|
|
20
|
+
*/
|
|
21
|
+
export interface TocEntry {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
/** Nesting level (1 = top-level). */
|
|
25
|
+
level: number;
|
|
26
|
+
}
|
|
27
|
+
export interface TocState {
|
|
28
|
+
items: TocEntry[];
|
|
29
|
+
activeId: string | null;
|
|
30
|
+
/** Ids of entries the user has manually expanded (for collapsible sub-levels). */
|
|
31
|
+
expanded: string[];
|
|
32
|
+
}
|
|
33
|
+
export type TocMsg = {
|
|
34
|
+
type: 'setItems';
|
|
35
|
+
items: TocEntry[];
|
|
36
|
+
} | {
|
|
37
|
+
type: 'setActive';
|
|
38
|
+
id: string | null;
|
|
39
|
+
} | {
|
|
40
|
+
type: 'toggleExpanded';
|
|
41
|
+
id: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'expandAll';
|
|
44
|
+
} | {
|
|
45
|
+
type: 'collapseAll';
|
|
46
|
+
};
|
|
47
|
+
export interface TocInit {
|
|
48
|
+
items?: TocEntry[];
|
|
49
|
+
activeId?: string | null;
|
|
50
|
+
expanded?: string[];
|
|
51
|
+
}
|
|
52
|
+
export declare function init(opts?: TocInit): TocState;
|
|
53
|
+
export declare function update(state: TocState, msg: TocMsg): [TocState, never[]];
|
|
54
|
+
export declare function isActive(state: TocState, id: string): boolean;
|
|
55
|
+
export declare function isExpanded(state: TocState, id: string): boolean;
|
|
56
|
+
export interface TocItemParts<S> {
|
|
57
|
+
item: {
|
|
58
|
+
'data-scope': 'toc';
|
|
59
|
+
'data-part': 'item';
|
|
60
|
+
'data-level': string;
|
|
61
|
+
'data-active': (s: S) => '' | undefined;
|
|
62
|
+
'data-value': string;
|
|
63
|
+
};
|
|
64
|
+
link: {
|
|
65
|
+
href: string;
|
|
66
|
+
'aria-current': (s: S) => 'location' | undefined;
|
|
67
|
+
'data-scope': 'toc';
|
|
68
|
+
'data-part': 'link';
|
|
69
|
+
'data-active': (s: S) => '' | undefined;
|
|
70
|
+
};
|
|
71
|
+
expandTrigger: {
|
|
72
|
+
type: 'button';
|
|
73
|
+
'aria-expanded': (s: S) => boolean;
|
|
74
|
+
'aria-label': string;
|
|
75
|
+
'data-scope': 'toc';
|
|
76
|
+
'data-part': 'expand-trigger';
|
|
77
|
+
'data-state': (s: S) => 'open' | 'closed';
|
|
78
|
+
onClick: (e: MouseEvent) => void;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export interface TocParts<S> {
|
|
82
|
+
root: {
|
|
83
|
+
role: 'navigation';
|
|
84
|
+
'aria-label': string;
|
|
85
|
+
'data-scope': 'toc';
|
|
86
|
+
'data-part': 'root';
|
|
87
|
+
};
|
|
88
|
+
list: {
|
|
89
|
+
role: 'list';
|
|
90
|
+
'data-scope': 'toc';
|
|
91
|
+
'data-part': 'list';
|
|
92
|
+
};
|
|
93
|
+
item: (entry: TocEntry) => TocItemParts<S>;
|
|
94
|
+
}
|
|
95
|
+
export interface ConnectOptions {
|
|
96
|
+
label?: string;
|
|
97
|
+
/** Prefix for href targets (default: '#'). */
|
|
98
|
+
hrefPrefix?: string;
|
|
99
|
+
expandLabel?: string;
|
|
100
|
+
}
|
|
101
|
+
export declare function connect<S>(get: (s: S) => TocState, send: Send<TocMsg>, opts?: ConnectOptions): TocParts<S>;
|
|
102
|
+
/**
|
|
103
|
+
* Install an IntersectionObserver that watches heading elements and
|
|
104
|
+
* dispatches `setActive` as the user scrolls. Call from onMount and
|
|
105
|
+
* invoke the returned function on unmount.
|
|
106
|
+
*
|
|
107
|
+
* `rootMargin` defaults to '0px 0px -80% 0px' — a heading is considered
|
|
108
|
+
* active once its top edge enters the top 20% of the viewport.
|
|
109
|
+
*/
|
|
110
|
+
export declare function watchActiveHeading(send: Send<TocMsg>, selector?: string, rootMargin?: string): () => void;
|
|
111
|
+
export declare const toc: {
|
|
112
|
+
init: typeof init;
|
|
113
|
+
update: typeof update;
|
|
114
|
+
connect: typeof connect;
|
|
115
|
+
isActive: typeof isActive;
|
|
116
|
+
isExpanded: typeof isExpanded;
|
|
117
|
+
watchActiveHeading: typeof watchActiveHeading;
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=toc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toc.d.ts","sourceRoot":"","sources":["../../src/components/toc.ts"],"names":[],"mappings":"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"}
|