@joewinke/jatui 0.39.0 → 0.41.0
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/CLAUDE.md +52 -5
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/dist/actions/scrollEdge.d.ts +55 -0
- package/dist/actions/scrollEdge.js +144 -0
- package/dist/artifact-blocks/blocks/diagram/DiagramBlock.svelte.d.ts +1 -1
- package/dist/artifact-blocks/blocks/diagram/schema.d.ts +1 -1
- package/dist/components/AnimatedNumber.svelte +42 -1
- package/dist/components/AnimatedNumber.svelte.d.ts +7 -0
- package/dist/components/BottomSheet.svelte +7 -6
- package/dist/components/BottomSheet.svelte.d.ts +2 -0
- package/dist/components/CloseButton.svelte +75 -0
- package/dist/components/CloseButton.svelte.d.ts +29 -0
- package/dist/components/Drawer.svelte +10 -11
- package/dist/components/Drawer.svelte.d.ts +2 -8
- package/dist/components/IconButton.svelte +200 -0
- package/dist/components/IconButton.svelte.d.ts +46 -0
- package/dist/components/LinkButton.svelte +117 -0
- package/dist/components/LinkButton.svelte.d.ts +32 -0
- package/dist/components/MediaLightbox.svelte +2 -7
- package/dist/components/Modal.svelte +2 -10
- package/dist/components/PinInput.svelte +94 -31
- package/dist/components/PinInput.svelte.d.ts +4 -0
- package/dist/components/SegmentedControl.svelte +263 -0
- package/dist/components/SegmentedControl.svelte.d.ts +48 -0
- package/dist/components/TextArea.svelte +67 -5
- package/dist/components/TextArea.svelte.d.ts +4 -6
- package/dist/components/ThemeSelector.svelte +29 -9
- package/dist/components/ThemeSelector.svelte.d.ts +6 -0
- package/dist/components/ThemeToggle.svelte +28 -3
- package/dist/components/ThemeToggle.svelte.d.ts +9 -0
- package/dist/components/basic/Breadcrumb.svelte +226 -11
- package/dist/components/basic/Breadcrumb.svelte.d.ts +27 -1
- package/dist/components/basic/breadcrumbCollapse.d.ts +19 -0
- package/dist/components/basic/breadcrumbCollapse.js +12 -0
- package/dist/components/graph/GraphCanvas.svelte.d.ts +2 -2
- package/dist/components/settings/SettingsRow.svelte +177 -0
- package/dist/components/settings/SettingsRow.svelte.d.ts +32 -0
- package/dist/components/settings/SettingsSection.svelte +139 -0
- package/dist/components/settings/SettingsSection.svelte.d.ts +21 -0
- package/dist/components/stats/ActivityHeatmap.svelte +412 -0
- package/dist/components/stats/ActivityHeatmap.svelte.d.ts +72 -0
- package/dist/components/stats/BarList.svelte +262 -0
- package/dist/components/stats/BarList.svelte.d.ts +57 -0
- package/dist/components/stats/ChartCard.svelte +230 -0
- package/dist/components/stats/ChartCard.svelte.d.ts +63 -0
- package/dist/components/stats/StatCard.svelte +387 -0
- package/dist/components/stats/StatCard.svelte.d.ts +37 -0
- package/dist/components/stats/StatGroup.svelte +67 -0
- package/dist/components/stats/StatGroup.svelte.d.ts +14 -0
- package/dist/components/stats/heatmap.d.ts +76 -0
- package/dist/components/stats/heatmap.js +151 -0
- package/dist/components/tree/TreeView.svelte +680 -0
- package/dist/components/tree/TreeView.svelte.d.ts +123 -0
- package/dist/components/tree/flattenTree.d.ts +57 -0
- package/dist/components/tree/flattenTree.js +91 -0
- package/dist/components/tree/treeKeys.d.ts +46 -0
- package/dist/components/tree/treeKeys.js +80 -0
- package/dist/data/grid/cells.d.ts +40 -0
- package/dist/data/grid/cells.js +89 -0
- package/dist/data/grid/clipboard.d.ts +46 -0
- package/dist/data/grid/clipboard.js +131 -0
- package/dist/data/grid/edits.d.ts +148 -0
- package/dist/data/grid/edits.js +222 -0
- package/dist/data/grid/index.d.ts +12 -0
- package/dist/data/grid/index.js +12 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +31 -1
- package/dist/styles/tokens.css +80 -1
- package/dist/utils/pinInput.d.ts +33 -0
- package/dist/utils/pinInput.js +63 -0
- package/dist/utils/textareaRows.d.ts +23 -0
- package/dist/utils/textareaRows.js +13 -0
- package/dist/utils/themeWipe.d.ts +36 -17
- package/dist/utils/themeWipe.js +145 -35
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -112,6 +112,10 @@ export { default as InfiniteSearchGrid } from './components/InfiniteSearchGrid.s
|
|
|
112
112
|
export { createInfiniteLoader } from './components/infiniteGridLoader';
|
|
113
113
|
// Components — from Flush
|
|
114
114
|
export { default as Button } from './components/Button.svelte';
|
|
115
|
+
// Icon-only / close / text-link buttons (BoardUI wave 1, jatui-owg2z)
|
|
116
|
+
export { default as IconButton } from './components/IconButton.svelte';
|
|
117
|
+
export { default as CloseButton } from './components/CloseButton.svelte';
|
|
118
|
+
export { default as LinkButton } from './components/LinkButton.svelte';
|
|
115
119
|
export { default as Badge } from './components/Badge.svelte';
|
|
116
120
|
export { default as Card } from './components/Card.svelte';
|
|
117
121
|
export { default as Drawer } from './components/Drawer.svelte';
|
|
@@ -120,6 +124,7 @@ export { default as LoadingSpinner } from './components/LoadingSpinner.svelte';
|
|
|
120
124
|
export { default as StatusBadge } from './components/StatusBadge.svelte';
|
|
121
125
|
export { default as TextInput } from './components/TextInput.svelte';
|
|
122
126
|
export { default as TextArea } from './components/TextArea.svelte';
|
|
127
|
+
export { textareaRowsHeight } from './utils/textareaRows';
|
|
123
128
|
export { default as SelectInput } from './components/SelectInput.svelte';
|
|
124
129
|
export { default as PhoneInput } from './components/PhoneInput.svelte';
|
|
125
130
|
// Components — from Chimaro
|
|
@@ -245,6 +250,8 @@ export { loadGoogleMapsAPI, isGoogleMapsLoaded, isPlacesLoaded, isGeometryLoaded
|
|
|
245
250
|
// ============================================================
|
|
246
251
|
// Motion primitives
|
|
247
252
|
export { tooltip } from './actions/tooltip';
|
|
253
|
+
// Progressive-blur scroll-region edge (BoardUI wave 1, jatui-owg2z)
|
|
254
|
+
export { scrollEdge, edgeOpacity, edgeDistances } from './actions/scrollEdge';
|
|
248
255
|
export { staggerEntrance } from './actions/staggerEntrance';
|
|
249
256
|
export { attention } from './actions/attention';
|
|
250
257
|
// Scroll-reveal (jatui-m4uz3.3, chimaro port): IntersectionObserver
|
|
@@ -258,7 +265,7 @@ export { default as MediaExplorer } from "./components/MediaExplorer.svelte";
|
|
|
258
265
|
export { floating, makeVirtualAnchor } from './actions/floating';
|
|
259
266
|
export { glide } from './transitions/glide';
|
|
260
267
|
// View Transitions kit
|
|
261
|
-
export { themeWipe } from './utils/themeWipe';
|
|
268
|
+
export { themeWipe, wipeOrigin, coverRadius } from './utils/themeWipe';
|
|
262
269
|
export { viewTransition } from './utils/viewTransition';
|
|
263
270
|
// Motion level kit (jatui-j6833): data-motion="calm|standard|lively" scales
|
|
264
271
|
// the --motion-* tokens per surface (styles/motion.css); these helpers,
|
|
@@ -292,6 +299,7 @@ export { default as CopyButton } from './components/CopyButton.svelte';
|
|
|
292
299
|
export { default as HoldToConfirm } from './components/HoldToConfirm.svelte';
|
|
293
300
|
// Form gaps
|
|
294
301
|
export { default as PinInput } from './components/PinInput.svelte';
|
|
302
|
+
export { applyCellInput as applyPinCellInput, distribute as distributePin, cellsFromValue as pinCellsFromValue, isPinComplete, sanitizePin } from './utils/pinInput';
|
|
295
303
|
export { default as PasswordMeter } from './components/PasswordMeter.svelte';
|
|
296
304
|
export { default as QuantityStepper } from './components/QuantityStepper.svelte';
|
|
297
305
|
// Messaging
|
|
@@ -321,6 +329,7 @@ export { createStickToBottom } from './actions/stickToBottom';
|
|
|
321
329
|
// ANSI terminal output → HTML (upstreamed from the JAT IDE, U5 — jatui-rl7w6.3).
|
|
322
330
|
export { ansiToHtml, ansiToHtmlStateful, ansiToHtmlWithLinks, ansiToHtmlWithHierarchy, linkifyUrls, stripAnsi, classifyLine, parseOutputLines, createDefaultAnsiState } from './utils/ansiToHtml';
|
|
323
331
|
export { default as Breadcrumb } from './components/basic/Breadcrumb.svelte';
|
|
332
|
+
export { collapseBreadcrumb } from './components/basic/breadcrumbCollapse';
|
|
324
333
|
export { default as Alert } from './components/basic/Alert.svelte';
|
|
325
334
|
export { default as ButtonGroup } from './components/basic/ButtonGroup.svelte';
|
|
326
335
|
export { default as Switch } from './components/basic/Switch.svelte';
|
|
@@ -384,3 +393,24 @@ export { splitStreamedText } from './components/agent/streamedText';
|
|
|
384
393
|
export { stepsSummary, stepsRemaining, isStepsFinished, clampStepProgress } from './components/progress/stepsSummary';
|
|
385
394
|
export { default as NotificationCenter } from './components/NotificationCenter.svelte';
|
|
386
395
|
export { DEFAULT_NOTIFICATION_TABS, DATE_GROUP_LABELS as NOTIFICATION_DATE_GROUP_LABELS, dateGroupKey as notificationDateGroupKey, groupNotificationsByDate, toTimestampMs as notificationTimestampMs, isNotificationUnread, unreadIds as unreadNotificationIds, filterByTab as filterNotificationsByTab, tabCounts as notificationTabCounts, unreadSummary as notificationUnreadSummary } from './components/notificationCenter';
|
|
396
|
+
// BoardUI wave 3 (jatui-6kitr) — tree, data and dashboards.
|
|
397
|
+
// TreeView renders flattenTree's flat row list (role="tree", aria-level/
|
|
398
|
+
// posinset/setsize, connector guides, full APG keys + typeahead, optional
|
|
399
|
+
// jatui/virtual windowing). Stats: StatCard/StatGroup (AnimatedNumber's new
|
|
400
|
+
// mode="tween"), ChartCard (headline driven by chart hover, no chart lib),
|
|
401
|
+
// BarList, ActivityHeatmap. SegmentedControl = radio semantics (Tabs switch
|
|
402
|
+
// panels; this sets a value). SettingsSection/SettingsRow = settings pages.
|
|
403
|
+
// Pure grid helpers also live on the 'jatui/grid' subpath.
|
|
404
|
+
export { default as TreeView } from './components/tree/TreeView.svelte';
|
|
405
|
+
export { flattenTree, collectBranchIds, pathToNode } from './components/tree/flattenTree';
|
|
406
|
+
export { resolveTreeKey, treeTypeahead } from './components/tree/treeKeys';
|
|
407
|
+
export { default as StatCard } from './components/stats/StatCard.svelte';
|
|
408
|
+
export { default as StatGroup } from './components/stats/StatGroup.svelte';
|
|
409
|
+
export { default as ChartCard } from './components/stats/ChartCard.svelte';
|
|
410
|
+
export { default as BarList } from './components/stats/BarList.svelte';
|
|
411
|
+
export { default as ActivityHeatmap } from './components/stats/ActivityHeatmap.svelte';
|
|
412
|
+
export { buildHeatmap, moveHeatFocus, isoDay as heatmapIsoDay, defaultLevelOf as heatmapLevelOf } from './components/stats/heatmap';
|
|
413
|
+
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
414
|
+
export { default as SettingsSection } from './components/settings/SettingsSection.svelte';
|
|
415
|
+
export { default as SettingsRow } from './components/settings/SettingsRow.svelte';
|
|
416
|
+
export * from './data/grid/index';
|
package/dist/styles/tokens.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* jatui design tokens — radius, spacing, border.
|
|
2
|
+
* jatui design tokens — radius, spacing, border, colour intent.
|
|
3
3
|
*
|
|
4
4
|
* Sibling of motion.css: jatui owns the token layer; DaisyUI theme
|
|
5
5
|
* variables FEED INTO it (never the other way around). Components use
|
|
@@ -29,3 +29,82 @@
|
|
|
29
29
|
/* Border — depth comes from tonal surfaces + 1px borders, not shadows */
|
|
30
30
|
--jatui-border-w: 1px;
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Colour-intent tokens (BoardUI wave 1, jatui-owg2z).
|
|
35
|
+
*
|
|
36
|
+
* A THIN component-intent layer: every value is DaisyUI's semantic colour
|
|
37
|
+
* vars mixed with `color-mix`, never a hex or a raw palette step, so a theme
|
|
38
|
+
* swap (and light ↔ dark) re-derives all of them for free. Mixing against
|
|
39
|
+
* --color-base-100 / --color-base-content is what makes the pairs flip in
|
|
40
|
+
* dark mode: the surface and the ink trade places, the mix follows.
|
|
41
|
+
*
|
|
42
|
+
* Declared on `:root` AND every `[data-theme]` element. Custom properties
|
|
43
|
+
* that contain var() are resolved where they are DECLARED, then inherited as
|
|
44
|
+
* finished colours — declared on :root alone, a nested `data-theme="dark"`
|
|
45
|
+
* panel would inherit the light root's values. Re-declaring them on each
|
|
46
|
+
* themed subtree makes them recompute against that subtree's palette.
|
|
47
|
+
*
|
|
48
|
+
* Components consume them with an inline fallback
|
|
49
|
+
* (`var(--jatui-focus-ring, …)`) so a host that never imports tokens.css
|
|
50
|
+
* still renders the same thing.
|
|
51
|
+
*/
|
|
52
|
+
:root,
|
|
53
|
+
[data-theme] {
|
|
54
|
+
/* Focus — one ring for every interactive part */
|
|
55
|
+
--jatui-focus-ring: color-mix(in oklab, var(--color-primary) 70%, transparent);
|
|
56
|
+
--jatui-focus-ring-width: 2px;
|
|
57
|
+
--jatui-focus-ring-offset: 2px;
|
|
58
|
+
|
|
59
|
+
/* Surfaces + quiet structure */
|
|
60
|
+
--jatui-surface: var(--color-base-100); /* what a scroll-edge fade blends into */
|
|
61
|
+
--jatui-hover-bg: color-mix(in oklab, var(--color-base-content) 6%, transparent);
|
|
62
|
+
--jatui-pressed-bg: color-mix(in oklab, var(--color-base-content) 10%, transparent);
|
|
63
|
+
--jatui-separator: color-mix(in oklab, var(--color-base-content) 12%, transparent);
|
|
64
|
+
|
|
65
|
+
/* Selection — selected rows, range bands, picked cells */
|
|
66
|
+
--jatui-selection-bg: color-mix(in oklab, var(--color-primary) 14%, transparent);
|
|
67
|
+
--jatui-selection-edge: color-mix(in oklab, var(--color-primary) 55%, transparent);
|
|
68
|
+
--jatui-selection-solid: var(--color-primary); /* range endpoints, the picked day */
|
|
69
|
+
--jatui-selection-solid-fg: var(--color-primary-content);
|
|
70
|
+
|
|
71
|
+
/* Chart — categorical series 1..5, plus the quiet chart furniture */
|
|
72
|
+
--jatui-chart-1: var(--color-primary);
|
|
73
|
+
--jatui-chart-2: var(--color-secondary);
|
|
74
|
+
--jatui-chart-3: var(--color-accent);
|
|
75
|
+
--jatui-chart-4: var(--color-info);
|
|
76
|
+
--jatui-chart-5: var(--color-warning);
|
|
77
|
+
--jatui-chart-grid: color-mix(in oklab, var(--color-base-content) 10%, transparent);
|
|
78
|
+
--jatui-chart-axis: color-mix(in oklab, var(--color-base-content) 55%, transparent);
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Event hues 1..6 — calendar chips, timeline bars, tags. Each hue is a
|
|
82
|
+
* triplet: a tinted chip surface, legible ink, and a solid edge/dot.
|
|
83
|
+
* bg mixes the hue into the surface; fg mixes it into the ink — both
|
|
84
|
+
* flip with the theme.
|
|
85
|
+
*/
|
|
86
|
+
--jatui-event-1: var(--color-primary);
|
|
87
|
+
--jatui-event-2: var(--color-secondary);
|
|
88
|
+
--jatui-event-3: var(--color-accent);
|
|
89
|
+
--jatui-event-4: var(--color-info);
|
|
90
|
+
--jatui-event-5: var(--color-success);
|
|
91
|
+
--jatui-event-6: var(--color-warning);
|
|
92
|
+
--jatui-event-1-bg: color-mix(in oklab, var(--jatui-event-1) 16%, var(--color-base-100));
|
|
93
|
+
--jatui-event-2-bg: color-mix(in oklab, var(--jatui-event-2) 16%, var(--color-base-100));
|
|
94
|
+
--jatui-event-3-bg: color-mix(in oklab, var(--jatui-event-3) 16%, var(--color-base-100));
|
|
95
|
+
--jatui-event-4-bg: color-mix(in oklab, var(--jatui-event-4) 16%, var(--color-base-100));
|
|
96
|
+
--jatui-event-5-bg: color-mix(in oklab, var(--jatui-event-5) 16%, var(--color-base-100));
|
|
97
|
+
--jatui-event-6-bg: color-mix(in oklab, var(--jatui-event-6) 16%, var(--color-base-100));
|
|
98
|
+
--jatui-event-1-fg: color-mix(in oklab, var(--jatui-event-1) 55%, var(--color-base-content));
|
|
99
|
+
--jatui-event-2-fg: color-mix(in oklab, var(--jatui-event-2) 55%, var(--color-base-content));
|
|
100
|
+
--jatui-event-3-fg: color-mix(in oklab, var(--jatui-event-3) 55%, var(--color-base-content));
|
|
101
|
+
--jatui-event-4-fg: color-mix(in oklab, var(--jatui-event-4) 55%, var(--color-base-content));
|
|
102
|
+
--jatui-event-5-fg: color-mix(in oklab, var(--jatui-event-5) 55%, var(--color-base-content));
|
|
103
|
+
--jatui-event-6-fg: color-mix(in oklab, var(--jatui-event-6) 55%, var(--color-base-content));
|
|
104
|
+
|
|
105
|
+
/* Status intents — availability dots, deltas, soft banners */
|
|
106
|
+
--jatui-status-positive: var(--color-success);
|
|
107
|
+
--jatui-status-caution: var(--color-warning);
|
|
108
|
+
--jatui-status-negative: var(--color-error);
|
|
109
|
+
--jatui-status-neutral: color-mix(in oklab, var(--color-base-content) 40%, transparent);
|
|
110
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure cell logic behind PinInput (jatui-owg2z). No DOM — unit-tested.
|
|
3
|
+
*
|
|
4
|
+
* One rule set covers typing, paste and OS one-time-code autofill:
|
|
5
|
+
* - a single character replaces the cell and advances;
|
|
6
|
+
* - several characters DISTRIBUTE from the cell they landed in (paste from
|
|
7
|
+
* the caret);
|
|
8
|
+
* - a whole code (≥ length characters) fills from the FIRST cell no matter
|
|
9
|
+
* which cell received it — that is what iOS/Android SMS autofill and
|
|
10
|
+
* Chrome's WebOTP do: they drop the full code into the focused field.
|
|
11
|
+
*/
|
|
12
|
+
export interface PinCellOptions {
|
|
13
|
+
length: number;
|
|
14
|
+
numeric?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface PinCellResult {
|
|
17
|
+
cells: string[];
|
|
18
|
+
/** Index to focus next. */
|
|
19
|
+
focus: number;
|
|
20
|
+
}
|
|
21
|
+
/** Strip whitespace and separators; digits only when numeric. */
|
|
22
|
+
export declare function sanitizePin(raw: string, numeric?: boolean): string;
|
|
23
|
+
/** Normalise an external value into exactly `length` cells. */
|
|
24
|
+
export declare function cellsFromValue(value: string, length: number, numeric?: boolean): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Apply what the browser put into cell `index`. `raw` is the cell's whole
|
|
27
|
+
* new value (with no maxlength, a typed character arrives NEXT TO the old
|
|
28
|
+
* one — "3" + "7" → "37" — so the previous char is dropped first).
|
|
29
|
+
*/
|
|
30
|
+
export declare function applyCellInput(cells: readonly string[], index: number, raw: string, { length, numeric }: PinCellOptions): PinCellResult;
|
|
31
|
+
/** Paste / autofill: spread `text` across cells starting at `start`. */
|
|
32
|
+
export declare function distribute(cells: readonly string[], start: number, text: string, { length, numeric }: PinCellOptions): PinCellResult;
|
|
33
|
+
export declare function isPinComplete(cells: readonly string[], length: number): boolean;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure cell logic behind PinInput (jatui-owg2z). No DOM — unit-tested.
|
|
3
|
+
*
|
|
4
|
+
* One rule set covers typing, paste and OS one-time-code autofill:
|
|
5
|
+
* - a single character replaces the cell and advances;
|
|
6
|
+
* - several characters DISTRIBUTE from the cell they landed in (paste from
|
|
7
|
+
* the caret);
|
|
8
|
+
* - a whole code (≥ length characters) fills from the FIRST cell no matter
|
|
9
|
+
* which cell received it — that is what iOS/Android SMS autofill and
|
|
10
|
+
* Chrome's WebOTP do: they drop the full code into the focused field.
|
|
11
|
+
*/
|
|
12
|
+
/** Strip whitespace and separators; digits only when numeric. */
|
|
13
|
+
export function sanitizePin(raw, numeric = false) {
|
|
14
|
+
const s = raw.replace(/[\s\-–—·.]/g, '');
|
|
15
|
+
return numeric ? s.replace(/\D/g, '') : s;
|
|
16
|
+
}
|
|
17
|
+
/** Normalise an external value into exactly `length` cells. */
|
|
18
|
+
export function cellsFromValue(value, length, numeric = false) {
|
|
19
|
+
const chars = [...sanitizePin(value ?? '', numeric)].slice(0, length);
|
|
20
|
+
return Array.from({ length }, (_, i) => chars[i] ?? '');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Apply what the browser put into cell `index`. `raw` is the cell's whole
|
|
24
|
+
* new value (with no maxlength, a typed character arrives NEXT TO the old
|
|
25
|
+
* one — "3" + "7" → "37" — so the previous char is dropped first).
|
|
26
|
+
*/
|
|
27
|
+
export function applyCellInput(cells, index, raw, { length, numeric = false }) {
|
|
28
|
+
const next = Array.from({ length }, (_, i) => cells[i] ?? '');
|
|
29
|
+
let text = sanitizePin(raw, numeric);
|
|
30
|
+
const prev = next[index];
|
|
31
|
+
// Typed over an existing character: keep only what is new.
|
|
32
|
+
if (prev && [...text].length === 2 && text.includes(prev)) {
|
|
33
|
+
text = text.startsWith(prev) ? text.slice(prev.length) : text.slice(0, -prev.length);
|
|
34
|
+
}
|
|
35
|
+
const chars = [...text];
|
|
36
|
+
if (chars.length === 0) {
|
|
37
|
+
next[index] = '';
|
|
38
|
+
return { cells: next, focus: index };
|
|
39
|
+
}
|
|
40
|
+
if (chars.length === 1) {
|
|
41
|
+
next[index] = chars[0];
|
|
42
|
+
return { cells: next, focus: Math.min(index + 1, length - 1) };
|
|
43
|
+
}
|
|
44
|
+
return distribute(next, chars.length >= length ? 0 : index, text, { length, numeric });
|
|
45
|
+
}
|
|
46
|
+
/** Paste / autofill: spread `text` across cells starting at `start`. */
|
|
47
|
+
export function distribute(cells, start, text, { length, numeric = false }) {
|
|
48
|
+
const next = Array.from({ length }, (_, i) => cells[i] ?? '');
|
|
49
|
+
const chars = [...sanitizePin(text, numeric)];
|
|
50
|
+
const from = chars.length >= length ? 0 : start;
|
|
51
|
+
let i = from;
|
|
52
|
+
for (const c of chars) {
|
|
53
|
+
if (i >= length)
|
|
54
|
+
break;
|
|
55
|
+
next[i++] = c;
|
|
56
|
+
}
|
|
57
|
+
const firstEmpty = next.findIndex((c) => !c);
|
|
58
|
+
const focus = firstEmpty === -1 ? length - 1 : Math.min(i, length - 1);
|
|
59
|
+
return { cells: next, focus };
|
|
60
|
+
}
|
|
61
|
+
export function isPinComplete(cells, length) {
|
|
62
|
+
return cells.length >= length && cells.slice(0, length).every((c) => c !== '');
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row-quantized auto-grow for TextArea (jatui-owg2z, BoardUI wave 1). Pure.
|
|
3
|
+
*
|
|
4
|
+
* Grows a whole line at a time between `minRows` and `maxRows`, then
|
|
5
|
+
* scrolls — no sub-line jitter while typing. `chrome` is the vertical
|
|
6
|
+
* padding + border in px (the element is border-box).
|
|
7
|
+
*/
|
|
8
|
+
export interface TextareaRowsInput {
|
|
9
|
+
/** The element's scrollHeight measured at `height: auto` (content + padding). */
|
|
10
|
+
scrollHeight: number;
|
|
11
|
+
lineHeight: number;
|
|
12
|
+
/** Vertical padding + border, px. */
|
|
13
|
+
chrome: number;
|
|
14
|
+
/** Vertical padding only, px (scrollHeight includes padding, not border). */
|
|
15
|
+
padding: number;
|
|
16
|
+
minRows: number;
|
|
17
|
+
maxRows?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function textareaRowsHeight(m: TextareaRowsInput): {
|
|
20
|
+
rows: number;
|
|
21
|
+
height: number;
|
|
22
|
+
overflow: 'hidden' | 'auto';
|
|
23
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function textareaRowsHeight(m) {
|
|
2
|
+
const lh = m.lineHeight > 0 ? m.lineHeight : 20;
|
|
3
|
+
const min = Math.max(1, Math.floor(m.minRows));
|
|
4
|
+
const max = m.maxRows != null ? Math.max(min, Math.floor(m.maxRows)) : Infinity;
|
|
5
|
+
// Content lines; a hair of tolerance so 3.0001 lines is still 3.
|
|
6
|
+
const contentLines = Math.ceil((m.scrollHeight - m.padding) / lh - 0.01);
|
|
7
|
+
const rows = Math.min(max, Math.max(min, contentLines));
|
|
8
|
+
return {
|
|
9
|
+
rows,
|
|
10
|
+
height: Math.round(rows * lh + m.chrome),
|
|
11
|
+
overflow: contentLines > rows ? 'auto' : 'hidden'
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -1,21 +1,40 @@
|
|
|
1
|
+
export interface ThemeWipeOptions {
|
|
2
|
+
/** 'hard' (default) clip-path circle, or 'soft' — a feathered mask edge. */
|
|
3
|
+
edge?: 'hard' | 'soft';
|
|
4
|
+
/** Standard-level duration in ms; scaled by the motion level. Default 500. */
|
|
5
|
+
duration?: number;
|
|
6
|
+
/** Explicit origin: a point, or an element whose centre is used. Overrides the event. */
|
|
7
|
+
origin?: {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
} | Element | null;
|
|
11
|
+
}
|
|
12
|
+
type Point = {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
1
16
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* click point, and animates ::view-transition-new(root) with a clip-path
|
|
6
|
-
* circle via WAAPI (~500ms, var(--motion-glide)).
|
|
7
|
-
*
|
|
8
|
-
* Falls back gracefully: if startViewTransition is unsupported or
|
|
9
|
-
* prefers-reduced-motion is set, applyTheme() runs synchronously with no
|
|
10
|
-
* animation.
|
|
17
|
+
* Where the wipe grows from. Pure apart from reading the element rect.
|
|
18
|
+
* Keyboard-synthesised clicks report clientX = clientY = 0; those use the
|
|
19
|
+
* element's centre instead of the top-left corner.
|
|
11
20
|
*/
|
|
21
|
+
export declare function wipeOrigin(event: Pick<MouseEvent, 'clientX' | 'clientY' | 'currentTarget' | 'target'> | null, viewport: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}, origin?: ThemeWipeOptions['origin']): Point;
|
|
25
|
+
/** Radius that covers the whole viewport from `p`. Pure. */
|
|
26
|
+
export declare function coverRadius(p: Point, viewport: {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
}): number;
|
|
30
|
+
/** WAAPI keyframes for the soft (mask) edge. Pure. */
|
|
31
|
+
export declare function softWipeKeyframes(p: Point, radius: number): Keyframe[];
|
|
12
32
|
/**
|
|
13
|
-
* @param event - The click
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* inside the startViewTransition callback so the snapshot
|
|
19
|
-
* captures the old state and the new DOM is recorded after.
|
|
33
|
+
* @param event - The click/pointer/keyboard event that triggered the toggle
|
|
34
|
+
* (anchors the circle). Pass null for the viewport centre.
|
|
35
|
+
* @param applyTheme - Performs the theme mutation (data-theme, storage). Runs
|
|
36
|
+
* inside startViewTransition so the old state is captured
|
|
37
|
+
* before it.
|
|
20
38
|
*/
|
|
21
|
-
export declare function themeWipe(event: MouseEvent | PointerEvent | null, applyTheme: () => void): Promise<void>;
|
|
39
|
+
export declare function themeWipe(event: MouseEvent | PointerEvent | KeyboardEvent | null, applyTheme: () => void, options?: ThemeWipeOptions): Promise<void>;
|
|
40
|
+
export {};
|
package/dist/utils/themeWipe.js
CHANGED
|
@@ -2,48 +2,158 @@
|
|
|
2
2
|
* themeWipe — circular View Transition reveal from a click origin.
|
|
3
3
|
*
|
|
4
4
|
* Wraps document.startViewTransition, computes a covering radius from the
|
|
5
|
-
*
|
|
6
|
-
* circle via WAAPI (~500ms, var(--motion-glide)).
|
|
5
|
+
* origin, and grows ::view-transition-new(root) from it with WAAPI.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* themeWipe(event, () => setTheme(next)) hard edge
|
|
8
|
+
* themeWipe(event, () => setTheme(next), { edge: 'soft' }) feathered edge
|
|
9
|
+
*
|
|
10
|
+
* Behaviour (BoardUI wave 1 fixes, jatui-owg2z):
|
|
11
|
+
* - Origin: the pointer position; a KEYBOARD activation (clientX/Y both 0)
|
|
12
|
+
* uses the centre of the element that fired it (`event.currentTarget`,
|
|
13
|
+
* or `options.origin`), never the top-left corner. No event → viewport
|
|
14
|
+
* centre.
|
|
15
|
+
* - Single-flight: a second call while a wipe is running skips the running
|
|
16
|
+
* transition and applies the new theme at once — no overlapping
|
|
17
|
+
* transitions on a double-click.
|
|
18
|
+
* - Motion tokens: duration is level-scaled (`data-motion` calm ×0.75,
|
|
19
|
+
* lively ×1.25) and the easing is `--motion-glide`. Reduced motion (or no
|
|
20
|
+
* View Transitions support) applies the theme synchronously.
|
|
21
|
+
* - The UA's default root cross-fade is suppressed for the duration, so the
|
|
22
|
+
* circle is the only thing that moves.
|
|
23
|
+
*
|
|
24
|
+
* The returned promise resolves once the theme is applied and any animation
|
|
25
|
+
* has finished.
|
|
11
26
|
*/
|
|
12
|
-
|
|
27
|
+
import { getMotionContext, motionDuration } from '../motion/level';
|
|
13
28
|
const EASING = 'cubic-bezier(0.16, 1, 0.3, 1)'; // --motion-glide fallback
|
|
29
|
+
/** Standard-level duration, ms (scaled by the motion level). */
|
|
30
|
+
const BASE_DURATION = 500;
|
|
31
|
+
/** Soft edge: the opaque core is this fraction of the mask circle's radius. */
|
|
32
|
+
const SOFT_CORE = 0.82;
|
|
33
|
+
function centreOf(el) {
|
|
34
|
+
const r = el.getBoundingClientRect();
|
|
35
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
36
|
+
}
|
|
14
37
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @param applyTheme - Callback that performs the actual theme mutation
|
|
19
|
-
* (e.g. set data-theme, update localStorage). Called
|
|
20
|
-
* inside the startViewTransition callback so the snapshot
|
|
21
|
-
* captures the old state and the new DOM is recorded after.
|
|
38
|
+
* Where the wipe grows from. Pure apart from reading the element rect.
|
|
39
|
+
* Keyboard-synthesised clicks report clientX = clientY = 0; those use the
|
|
40
|
+
* element's centre instead of the top-left corner.
|
|
22
41
|
*/
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
export function wipeOrigin(event, viewport, origin) {
|
|
43
|
+
if (origin) {
|
|
44
|
+
if ('getBoundingClientRect' in origin)
|
|
45
|
+
return centreOf(origin);
|
|
46
|
+
return origin;
|
|
47
|
+
}
|
|
48
|
+
if (event) {
|
|
49
|
+
// KeyboardEvents carry no clientX/Y at all; synthesised clicks carry 0,0.
|
|
50
|
+
const cx = typeof event.clientX === 'number' ? event.clientX : 0;
|
|
51
|
+
const cy = typeof event.clientY === 'number' ? event.clientY : 0;
|
|
52
|
+
if (cx !== 0 || cy !== 0)
|
|
53
|
+
return { x: cx, y: cy };
|
|
54
|
+
const el = (event.currentTarget ?? event.target);
|
|
55
|
+
if (el && typeof el.getBoundingClientRect === 'function')
|
|
56
|
+
return centreOf(el);
|
|
57
|
+
}
|
|
58
|
+
return { x: viewport.width / 2, y: viewport.height / 2 };
|
|
59
|
+
}
|
|
60
|
+
/** Radius that covers the whole viewport from `p`. Pure. */
|
|
61
|
+
export function coverRadius(p, viewport) {
|
|
62
|
+
return Math.hypot(Math.max(p.x, viewport.width - p.x), Math.max(p.y, viewport.height - p.y));
|
|
63
|
+
}
|
|
64
|
+
/** WAAPI keyframes for the soft (mask) edge. Pure. */
|
|
65
|
+
export function softWipeKeyframes(p, radius) {
|
|
66
|
+
const end = (2 * radius) / SOFT_CORE;
|
|
67
|
+
const frame = (size) => ({
|
|
68
|
+
maskSize: `${size}px ${size}px`,
|
|
69
|
+
maskPosition: `${p.x - size / 2}px ${p.y - size / 2}px`,
|
|
70
|
+
webkitMaskSize: `${size}px ${size}px`,
|
|
71
|
+
webkitMaskPosition: `${p.x - size / 2}px ${p.y - size / 2}px`
|
|
72
|
+
});
|
|
73
|
+
return [frame(0), frame(end)];
|
|
74
|
+
}
|
|
75
|
+
let running = null;
|
|
76
|
+
const STYLE_ID = 'jatui-theme-wipe-style';
|
|
77
|
+
function setWipeStyle(edge) {
|
|
78
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
79
|
+
if (!edge)
|
|
80
|
+
return;
|
|
81
|
+
const style = document.createElement('style');
|
|
82
|
+
style.id = STYLE_ID;
|
|
83
|
+
const mask = edge === 'soft'
|
|
84
|
+
? `mask-image: radial-gradient(closest-side, #000 ${SOFT_CORE * 100}%, transparent);
|
|
85
|
+
-webkit-mask-image: radial-gradient(closest-side, #000 ${SOFT_CORE * 100}%, transparent);
|
|
86
|
+
mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
|
|
87
|
+
mask-size: 0 0; -webkit-mask-size: 0 0;`
|
|
88
|
+
: '';
|
|
89
|
+
style.textContent = `
|
|
90
|
+
::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
|
|
91
|
+
::view-transition-new(root) { ${mask} }
|
|
92
|
+
`;
|
|
93
|
+
document.head.appendChild(style);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* @param event - The click/pointer/keyboard event that triggered the toggle
|
|
97
|
+
* (anchors the circle). Pass null for the viewport centre.
|
|
98
|
+
* @param applyTheme - Performs the theme mutation (data-theme, storage). Runs
|
|
99
|
+
* inside startViewTransition so the old state is captured
|
|
100
|
+
* before it.
|
|
101
|
+
*/
|
|
102
|
+
export async function themeWipe(event, applyTheme, options = {}) {
|
|
103
|
+
if (typeof document === 'undefined') {
|
|
104
|
+
applyTheme();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Single-flight: finish the running wipe instantly, then apply this one plainly.
|
|
108
|
+
if (running) {
|
|
109
|
+
const prev = running;
|
|
110
|
+
running = null;
|
|
111
|
+
prev.skipTransition();
|
|
112
|
+
// Its update callback still runs (asynchronously) — let it land first,
|
|
113
|
+
// or it would overwrite the theme applied here.
|
|
114
|
+
await prev.updateCallbackDone.catch(() => { });
|
|
26
115
|
applyTheme();
|
|
27
116
|
return;
|
|
28
117
|
}
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
118
|
+
const ctx = getMotionContext();
|
|
119
|
+
const duration = motionDuration(options.duration ?? BASE_DURATION, ctx);
|
|
120
|
+
if (ctx.reduced || duration <= 0 || !document.startViewTransition) {
|
|
121
|
+
applyTheme();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const viewport = { width: window.innerWidth, height: window.innerHeight };
|
|
125
|
+
const p = wipeOrigin(event, viewport, options.origin);
|
|
126
|
+
const radius = coverRadius(p, viewport);
|
|
127
|
+
const edge = options.edge ?? 'hard';
|
|
128
|
+
setWipeStyle(edge);
|
|
34
129
|
const transition = document.startViewTransition(applyTheme);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
130
|
+
running = transition;
|
|
131
|
+
try {
|
|
132
|
+
await transition.ready;
|
|
133
|
+
const easing = getComputedStyle(document.documentElement).getPropertyValue('--motion-glide').trim() ||
|
|
134
|
+
EASING;
|
|
135
|
+
const keyframes = edge === 'soft'
|
|
136
|
+
? softWipeKeyframes(p, radius)
|
|
137
|
+
: [
|
|
138
|
+
{ clipPath: `circle(0px at ${p.x}px ${p.y}px)` },
|
|
139
|
+
{ clipPath: `circle(${radius}px at ${p.x}px ${p.y}px)` }
|
|
140
|
+
];
|
|
141
|
+
document.documentElement.animate(keyframes, {
|
|
142
|
+
duration,
|
|
143
|
+
easing,
|
|
144
|
+
fill: 'forwards',
|
|
145
|
+
pseudoElement: '::view-transition-new(root)'
|
|
146
|
+
});
|
|
147
|
+
await transition.finished;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Skipped or aborted — the theme callback has still run.
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
if (running === transition)
|
|
154
|
+
running = null;
|
|
155
|
+
// A newer wipe may own the style element by now — leave it alone.
|
|
156
|
+
if (!running)
|
|
157
|
+
setWipeStyle(null);
|
|
158
|
+
}
|
|
49
159
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joewinke/jatui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Svelte 5 component library for JAT projects",
|
|
6
6
|
"type": "module",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"svelte": "./dist/components/table/index.js",
|
|
35
35
|
"default": "./dist/components/table/index.js"
|
|
36
36
|
},
|
|
37
|
+
"./grid": {
|
|
38
|
+
"types": "./dist/data/grid/index.d.ts",
|
|
39
|
+
"default": "./dist/data/grid/index.js"
|
|
40
|
+
},
|
|
37
41
|
"./virtual": {
|
|
38
42
|
"types": "./dist/virtual/index.d.ts",
|
|
39
43
|
"svelte": "./dist/virtual/index.js",
|