@santi020k/lumen-core 3.0.1 → 4.0.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/PHONE_FLAG_LICENSE.txt +21 -0
- package/README.md +363 -3
- package/THIRD_PARTY_NOTICES.md +16 -2
- package/dist/ai-surfaces.d.ts +25 -0
- package/dist/ai-surfaces.d.ts.map +1 -0
- package/dist/ai-surfaces.js +162 -0
- package/dist/amount-field.d.ts +22 -0
- package/dist/amount-field.d.ts.map +1 -0
- package/dist/amount-field.js +207 -0
- package/dist/attachments.d.ts +15 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +123 -0
- package/dist/bullet-chart.d.ts +33 -0
- package/dist/bullet-chart.d.ts.map +1 -0
- package/dist/bullet-chart.js +80 -0
- package/dist/chart-activation.d.ts +32 -0
- package/dist/chart-activation.d.ts.map +1 -0
- package/dist/chart-activation.js +97 -0
- package/dist/chart-interaction.d.ts +12 -0
- package/dist/chart-interaction.d.ts.map +1 -0
- package/dist/chart-interaction.js +274 -0
- package/dist/chart-models.d.ts +156 -0
- package/dist/chart-models.d.ts.map +1 -0
- package/dist/chart-models.js +348 -0
- package/dist/chart-motion.d.ts +6 -0
- package/dist/chart-motion.d.ts.map +1 -0
- package/dist/chart-motion.js +107 -0
- package/dist/charts.d.ts +58 -3
- package/dist/charts.d.ts.map +1 -1
- package/dist/charts.js +247 -18
- package/dist/code.js +1 -1
- package/dist/combobox.d.ts +7 -0
- package/dist/combobox.d.ts.map +1 -0
- package/dist/combobox.js +253 -0
- package/dist/comparison-chart.d.ts +32 -0
- package/dist/comparison-chart.d.ts.map +1 -0
- package/dist/comparison-chart.js +64 -0
- package/dist/components.d.ts +210 -25
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +102 -26
- package/dist/dashboard.d.ts +16 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +21 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +21 -5
- package/dist/dates.d.ts +13 -0
- package/dist/dates.d.ts.map +1 -0
- package/dist/dates.js +37 -0
- package/dist/device-frame.d.ts +25 -0
- package/dist/device-frame.d.ts.map +1 -0
- package/dist/device-frame.js +50 -0
- package/dist/direction.d.ts +3 -0
- package/dist/direction.d.ts.map +1 -0
- package/dist/direction.js +10 -0
- package/dist/extended-charts.d.ts +95 -0
- package/dist/extended-charts.d.ts.map +1 -0
- package/dist/extended-charts.js +175 -0
- package/dist/figma.d.ts.map +1 -1
- package/dist/figma.js +10 -1
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +18 -4
- package/dist/icon-data.generated.d.ts +1 -1
- package/dist/icon-data.generated.d.ts.map +1 -1
- package/dist/icon-data.generated.js +1 -1
- package/dist/icons.js +3 -4
- package/dist/illustrations.generated.d.ts +1 -1
- package/dist/image-comparison.d.ts +14 -0
- package/dist/image-comparison.d.ts.map +1 -0
- package/dist/image-comparison.js +48 -0
- package/dist/index.d.ts +35 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -3
- package/dist/media-viewport.d.ts +24 -0
- package/dist/media-viewport.d.ts.map +1 -0
- package/dist/media-viewport.js +119 -0
- package/dist/media-workspace.d.ts +27 -0
- package/dist/media-workspace.d.ts.map +1 -0
- package/dist/media-workspace.js +83 -0
- package/dist/message-scroller.d.ts +14 -0
- package/dist/message-scroller.d.ts.map +1 -0
- package/dist/message-scroller.js +113 -0
- package/dist/motion-workflows.d.ts +24 -0
- package/dist/motion-workflows.d.ts.map +1 -0
- package/dist/motion-workflows.js +220 -0
- package/dist/motion.d.ts +12 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +96 -0
- package/dist/native-input.d.ts +25 -0
- package/dist/native-input.d.ts.map +1 -0
- package/dist/native-input.js +188 -0
- package/dist/phone-flags.generated.d.ts +2 -0
- package/dist/phone-flags.generated.d.ts.map +1 -0
- package/dist/phone-flags.generated.js +263 -0
- package/dist/phone.d.ts +4 -0
- package/dist/phone.d.ts.map +1 -1
- package/dist/phone.js +6 -1
- package/dist/rich-text.d.ts +7 -0
- package/dist/rich-text.d.ts.map +1 -1
- package/dist/rich-text.js +13 -0
- package/dist/rive.d.ts +37 -0
- package/dist/rive.d.ts.map +1 -0
- package/dist/rive.js +129 -0
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +63 -23
- package/dist/tab-indicator.d.ts +3 -0
- package/dist/tab-indicator.d.ts.map +1 -0
- package/dist/tab-indicator.js +57 -0
- package/dist/theme-audit.d.ts +16 -0
- package/dist/theme-audit.d.ts.map +1 -0
- package/dist/theme-audit.js +57 -0
- package/dist/theme-builder.d.ts +7 -1
- package/dist/theme-builder.d.ts.map +1 -1
- package/dist/theme-builder.js +45 -23
- package/dist/theme-presets.generated.d.ts +217 -0
- package/dist/theme-presets.generated.d.ts.map +1 -0
- package/dist/theme-presets.generated.js +231 -0
- package/dist/theme.d.ts +11 -3
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +56 -6
- package/dist/virtual-collection.d.ts +14 -0
- package/dist/virtual-collection.d.ts.map +1 -0
- package/dist/virtual-collection.js +132 -0
- package/dist/virtual-list.d.ts +7 -0
- package/dist/virtual-list.d.ts.map +1 -0
- package/dist/virtual-list.js +135 -0
- package/dist/virtual-window.d.ts +19 -0
- package/dist/virtual-window.d.ts.map +1 -0
- package/dist/virtual-window.js +84 -0
- package/dist/visual-effects.d.ts +6 -0
- package/dist/visual-effects.d.ts.map +1 -0
- package/dist/visual-effects.js +44 -0
- package/dist/world-map-data.generated.d.ts +9 -0
- package/dist/world-map-data.generated.d.ts.map +1 -0
- package/dist/world-map-data.generated.js +240 -0
- package/dist/world-map-navigation.d.ts +16 -0
- package/dist/world-map-navigation.d.ts.map +1 -0
- package/dist/world-map-navigation.js +53 -0
- package/dist/world-map-pan.d.ts +3 -0
- package/dist/world-map-pan.d.ts.map +1 -0
- package/dist/world-map-pan.js +66 -0
- package/dist/world-map-zoom.d.ts +13 -0
- package/dist/world-map-zoom.d.ts.map +1 -0
- package/dist/world-map-zoom.js +61 -0
- package/dist/world-map.d.ts +71 -0
- package/dist/world-map.d.ts.map +1 -0
- package/dist/world-map.js +143 -0
- package/package.json +51 -4
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const assertMediaIdentities = (items) => {
|
|
2
|
+
const ids = new Set();
|
|
3
|
+
for (const item of items) {
|
|
4
|
+
if (!item.id.trim() || ids.has(item.id))
|
|
5
|
+
throw new Error('Media items require nonempty, unique IDs');
|
|
6
|
+
ids.add(item.id);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
/** Returns a new collection; stale requests and out-of-range destinations leave its order intact. */
|
|
10
|
+
export const moveLumenMediaItem = (items, id, targetIndex) => {
|
|
11
|
+
assertMediaIdentities(items);
|
|
12
|
+
const next = [...items];
|
|
13
|
+
const from = items.findIndex(item => item.id === id);
|
|
14
|
+
const item = items[from];
|
|
15
|
+
const validTarget = Number.isInteger(targetIndex) && targetIndex >= 0 && targetIndex < items.length;
|
|
16
|
+
if (!item || item.disabled || !validTarget)
|
|
17
|
+
return next;
|
|
18
|
+
next.splice(from, 1);
|
|
19
|
+
next.splice(targetIndex, 0, item);
|
|
20
|
+
return next;
|
|
21
|
+
};
|
|
22
|
+
/** Prunes missing IDs and duplicates, preserving the displayed media order. */
|
|
23
|
+
export const resolveLumenMediaSelection = (items, selectedIds) => {
|
|
24
|
+
assertMediaIdentities(items);
|
|
25
|
+
const selected = new Set(selectedIds);
|
|
26
|
+
return items.filter(item => selected.has(item.id)).map(item => item.id);
|
|
27
|
+
};
|
|
28
|
+
export const toggleLumenMediaSelection = (items, selectedIds, id) => {
|
|
29
|
+
const selected = new Set(resolveLumenMediaSelection(items, selectedIds));
|
|
30
|
+
const item = items.find(candidate => candidate.id === id);
|
|
31
|
+
if (!item || item.disabled)
|
|
32
|
+
return [...selected];
|
|
33
|
+
if (selected.has(id))
|
|
34
|
+
selected.delete(id);
|
|
35
|
+
else
|
|
36
|
+
selected.add(id);
|
|
37
|
+
return resolveLumenMediaSelection(items, [...selected]);
|
|
38
|
+
};
|
|
39
|
+
export const resolveLumenMediaMaxZoom = (maxZoom = 4) => (Number.isFinite(maxZoom) ? Math.min(16, Math.max(1, maxZoom)) : 4);
|
|
40
|
+
const panFraction = (value = 0) => Number.isFinite(value) ? Math.max(-1, Math.min(1, value)) : 0;
|
|
41
|
+
const viewportNumber = (value, fallback = 0) => typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
42
|
+
const isViewportRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
43
|
+
export const normalizeLumenMediaViewport = (value = {}, maxZoom = 4) => {
|
|
44
|
+
if (!isViewportRecord(value))
|
|
45
|
+
return { x: 0, y: 0, zoom: 1 };
|
|
46
|
+
const limit = resolveLumenMediaMaxZoom(maxZoom);
|
|
47
|
+
const zoom = Math.min(limit, Math.max(1, viewportNumber(value.zoom, 1)));
|
|
48
|
+
const x = panFraction(viewportNumber(value.x));
|
|
49
|
+
const y = panFraction(viewportNumber(value.y));
|
|
50
|
+
return { x: zoom === 1 ? 0 : x, y: zoom === 1 ? 0 : y, zoom };
|
|
51
|
+
};
|
|
52
|
+
export const applyLumenMediaViewportAction = (value, action, maxZoom = 4) => {
|
|
53
|
+
const current = normalizeLumenMediaViewport(value, maxZoom);
|
|
54
|
+
if (action === 'fit')
|
|
55
|
+
return { x: 0, y: 0, zoom: 1 };
|
|
56
|
+
switch (action) {
|
|
57
|
+
case 'zoom-in': return normalizeLumenMediaViewport({ ...current, zoom: current.zoom + 0.25 }, maxZoom);
|
|
58
|
+
case 'zoom-out': return normalizeLumenMediaViewport({ ...current, zoom: current.zoom - 0.25 }, maxZoom);
|
|
59
|
+
case 'left': return normalizeLumenMediaViewport({ ...current, x: current.x - 0.25 }, maxZoom);
|
|
60
|
+
case 'right': return normalizeLumenMediaViewport({ ...current, x: current.x + 0.25 }, maxZoom);
|
|
61
|
+
case 'up': return normalizeLumenMediaViewport({ ...current, y: current.y - 0.25 }, maxZoom);
|
|
62
|
+
case 'down': return normalizeLumenMediaViewport({ ...current, y: current.y + 0.25 }, maxZoom);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** Maps pointer movement to bounded pan coordinates without allowing media outside its viewport. */
|
|
66
|
+
export const panLumenMediaViewport = (value, deltaX, deltaY, width, height, maxZoom = 4) => {
|
|
67
|
+
const current = normalizeLumenMediaViewport(value, maxZoom);
|
|
68
|
+
const extent = (current.zoom - 1) / 2;
|
|
69
|
+
if (extent === 0 || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
70
|
+
return current;
|
|
71
|
+
return normalizeLumenMediaViewport({
|
|
72
|
+
...current,
|
|
73
|
+
x: current.x + (Number.isFinite(deltaX) ? deltaX / width / extent : 0),
|
|
74
|
+
y: current.y + (Number.isFinite(deltaY) ? deltaY / height / extent : 0)
|
|
75
|
+
}, maxZoom);
|
|
76
|
+
};
|
|
77
|
+
export const resolveLumenMediaThumbnailState = (state) => (state === 'loading' || state === 'error' ? state : 'ready');
|
|
78
|
+
export const resolveLumenMediaOrder = (order) => (order !== undefined && Number.isSafeInteger(order) && order > 0 ? order : undefined);
|
|
79
|
+
export const formatLumenMediaThumbnailState = (state, label) => {
|
|
80
|
+
if (label !== undefined)
|
|
81
|
+
return label;
|
|
82
|
+
return state === 'loading' ? 'Loading' : 'Unavailable';
|
|
83
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface LumenMessageScrollState {
|
|
2
|
+
atEnd: boolean;
|
|
3
|
+
}
|
|
4
|
+
export interface LumenMessageScrollerOptions {
|
|
5
|
+
threshold?: number;
|
|
6
|
+
onStateChange?: (state: LumenMessageScrollState) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface LumenMessageScrollerController {
|
|
9
|
+
destroy: () => void;
|
|
10
|
+
scrollToEnd: () => void;
|
|
11
|
+
}
|
|
12
|
+
/** Opt-in follow behavior. The application owns message identity and pagination. */
|
|
13
|
+
export declare const createLumenMessageScrollerController: (viewport: HTMLElement, options?: LumenMessageScrollerOptions) => LumenMessageScrollerController;
|
|
14
|
+
//# sourceMappingURL=message-scroller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-scroller.d.ts","sourceRoot":"","sources":["../src/message-scroller.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IAAG,KAAK,EAAE,OAAO,CAAA;CAAE;AAE3D,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAA;CACzD;AAED,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,WAAW,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,oFAAoF;AACpF,eAAO,MAAM,oCAAoC,GAC/C,UAAU,WAAW,EAAE,UAAS,2BAAgC,KAC/D,8BA0JF,CAAA"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Opt-in follow behavior. The application owns message identity and pagination. */
|
|
2
|
+
export const createLumenMessageScrollerController = (viewport, options = {}) => {
|
|
3
|
+
const view = viewport.ownerDocument.defaultView;
|
|
4
|
+
if (!view)
|
|
5
|
+
throw new Error('MessageScroller requires a document with a window.');
|
|
6
|
+
const threshold = options.threshold ?? 32;
|
|
7
|
+
if (!Number.isFinite(threshold) || threshold < 0)
|
|
8
|
+
throw new RangeError('MessageScroller threshold must be a finite nonnegative distance.');
|
|
9
|
+
let atEnd = true;
|
|
10
|
+
let anchor;
|
|
11
|
+
let frame;
|
|
12
|
+
let destroyed = false;
|
|
13
|
+
let programmaticTop;
|
|
14
|
+
const jump = viewport.querySelector('[data-ui-message-jump]');
|
|
15
|
+
const previousAnchor = viewport.style.overflowAnchor;
|
|
16
|
+
const previousBehavior = viewport.style.scrollBehavior;
|
|
17
|
+
const previousTabIndex = viewport.getAttribute('tabindex');
|
|
18
|
+
viewport.style.overflowAnchor = 'none';
|
|
19
|
+
viewport.style.scrollBehavior = 'auto';
|
|
20
|
+
if (previousTabIndex === null)
|
|
21
|
+
viewport.tabIndex = 0;
|
|
22
|
+
const captureAnchor = () => {
|
|
23
|
+
const top = viewport.getBoundingClientRect().top;
|
|
24
|
+
const item = [...viewport.querySelectorAll('[data-ui-message-item]')].find(element => element.getBoundingClientRect().bottom > top);
|
|
25
|
+
anchor = item ? { element: item, top: item.getBoundingClientRect().top } : undefined;
|
|
26
|
+
};
|
|
27
|
+
const announce = () => {
|
|
28
|
+
viewport.dataset.atEnd = String(atEnd);
|
|
29
|
+
if (jump)
|
|
30
|
+
jump.hidden = atEnd;
|
|
31
|
+
options.onStateChange?.({ atEnd });
|
|
32
|
+
viewport.dispatchEvent(new view.CustomEvent('ui:message-scroll-state', { bubbles: true, detail: { atEnd } }));
|
|
33
|
+
};
|
|
34
|
+
const scrollToEnd = () => {
|
|
35
|
+
atEnd = true;
|
|
36
|
+
// Hiding the jump action changes the scroll extent; measure its final layout.
|
|
37
|
+
announce();
|
|
38
|
+
viewport.scrollTop = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
|
|
39
|
+
programmaticTop = viewport.scrollTop;
|
|
40
|
+
captureAnchor();
|
|
41
|
+
};
|
|
42
|
+
const onScroll = () => {
|
|
43
|
+
if (viewport.scrollTop === programmaticTop) {
|
|
44
|
+
programmaticTop = undefined;
|
|
45
|
+
captureAnchor();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
programmaticTop = undefined;
|
|
49
|
+
const next = viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= threshold;
|
|
50
|
+
if (next !== atEnd) {
|
|
51
|
+
atEnd = next;
|
|
52
|
+
announce();
|
|
53
|
+
}
|
|
54
|
+
captureAnchor();
|
|
55
|
+
};
|
|
56
|
+
const refresh = () => {
|
|
57
|
+
if (frame !== undefined || destroyed)
|
|
58
|
+
return;
|
|
59
|
+
frame = view.requestAnimationFrame(() => {
|
|
60
|
+
frame = undefined;
|
|
61
|
+
if (destroyed)
|
|
62
|
+
return;
|
|
63
|
+
if (atEnd)
|
|
64
|
+
scrollToEnd();
|
|
65
|
+
else {
|
|
66
|
+
if (anchor?.element.isConnected && viewport.contains(anchor.element)) {
|
|
67
|
+
viewport.scrollTop += anchor.element.getBoundingClientRect().top - anchor.top;
|
|
68
|
+
}
|
|
69
|
+
captureAnchor();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
const resize = typeof view.ResizeObserver === 'function' ? new view.ResizeObserver(refresh) : undefined;
|
|
74
|
+
const observeContent = () => {
|
|
75
|
+
resize?.disconnect();
|
|
76
|
+
resize?.observe(viewport);
|
|
77
|
+
for (const child of viewport.children)
|
|
78
|
+
resize?.observe(child);
|
|
79
|
+
};
|
|
80
|
+
const mutation = new view.MutationObserver(records => {
|
|
81
|
+
if (!records.some(record => record.type === 'childList' || record.type === 'characterData'))
|
|
82
|
+
return;
|
|
83
|
+
observeContent();
|
|
84
|
+
refresh();
|
|
85
|
+
});
|
|
86
|
+
mutation.observe(viewport, { childList: true, characterData: true, subtree: true });
|
|
87
|
+
observeContent();
|
|
88
|
+
viewport.addEventListener('scroll', onScroll, { passive: true });
|
|
89
|
+
const jumpToEnd = () => {
|
|
90
|
+
const focused = viewport.ownerDocument.activeElement === jump;
|
|
91
|
+
scrollToEnd();
|
|
92
|
+
if (focused)
|
|
93
|
+
viewport.focus({ preventScroll: true });
|
|
94
|
+
};
|
|
95
|
+
jump?.addEventListener('click', jumpToEnd);
|
|
96
|
+
scrollToEnd();
|
|
97
|
+
return {
|
|
98
|
+
scrollToEnd,
|
|
99
|
+
destroy: () => {
|
|
100
|
+
destroyed = true;
|
|
101
|
+
mutation.disconnect();
|
|
102
|
+
resize?.disconnect();
|
|
103
|
+
viewport.removeEventListener('scroll', onScroll);
|
|
104
|
+
jump?.removeEventListener('click', jumpToEnd);
|
|
105
|
+
viewport.style.overflowAnchor = previousAnchor;
|
|
106
|
+
viewport.style.scrollBehavior = previousBehavior;
|
|
107
|
+
if (previousTabIndex === null)
|
|
108
|
+
viewport.removeAttribute('tabindex');
|
|
109
|
+
if (frame !== undefined)
|
|
110
|
+
view.cancelAnimationFrame(frame);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { lumenDurations } from './foundations.generated.js';
|
|
2
|
+
export type LumenWorkflowDuration = keyof typeof lumenDurations;
|
|
3
|
+
export interface LumenMotionGroupOptions {
|
|
4
|
+
duration?: LumenWorkflowDuration;
|
|
5
|
+
enterExit?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface LumenMotionGroupController {
|
|
8
|
+
destroy(): void;
|
|
9
|
+
refresh(): void;
|
|
10
|
+
update(change: () => void): void;
|
|
11
|
+
}
|
|
12
|
+
/** Resolve shared motion tokens without requiring a browser during import. */
|
|
13
|
+
export declare const getLumenWorkflowTiming: (element: Element, duration?: LumenWorkflowDuration) => {
|
|
14
|
+
duration: number;
|
|
15
|
+
easing: string;
|
|
16
|
+
};
|
|
17
|
+
export declare const isLumenWorkflowMotionReduced: (element: Element) => boolean;
|
|
18
|
+
/** Animate keyed direct children while leaving DOM ordering and focus with the application. */
|
|
19
|
+
export declare const createLumenMotionGroupController: (root: HTMLElement, options?: LumenMotionGroupOptions) => LumenMotionGroupController;
|
|
20
|
+
/** Observe application-owned child changes; animation ghosts never trigger another refresh. */
|
|
21
|
+
export declare const bindLumenMotionGroup: (root: HTMLElement, options?: LumenMotionGroupOptions) => (() => void);
|
|
22
|
+
/** Execute application updates exactly once, whether a view transition is available or not. */
|
|
23
|
+
export declare const runLumenViewTransition: (ownerDocument: Document, update: () => void | Promise<void>) => Promise<void>;
|
|
24
|
+
//# sourceMappingURL=motion-workflows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion-workflows.d.ts","sourceRoot":"","sources":["../src/motion-workflows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAgB,MAAM,4BAA4B,CAAA;AAEzE,MAAM,MAAM,qBAAqB,GAAG,MAAM,OAAO,cAAc,CAAA;AAE/D,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IAChC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,IAAI,IAAI,CAAA;IAEf,OAAO,IAAI,IAAI,CAAA;IAEf,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CACjC;AAiBD,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,GACjC,SAAS,OAAO,EAAE,WAAU,qBAAkC,KAC7D;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAUpC,CAAA;AAED,eAAO,MAAM,4BAA4B,GAAI,SAAS,OAAO,KAAG,OACoC,CAAA;AAgEpG,+FAA+F;AAC/F,eAAO,MAAM,gCAAgC,GAC3C,MAAM,WAAW,EAAE,UAAS,uBAA4B,KACvD,0BAkIF,CAAA;AAED,+FAA+F;AAC/F,eAAO,MAAM,oBAAoB,GAC/B,MAAM,WAAW,EAAE,UAAS,uBAA4B,KACvD,CAAC,MAAM,IAAI,CAqBb,CAAA;AAED,+FAA+F;AAC/F,eAAO,MAAM,sBAAsB,GACjC,eAAe,QAAQ,EAAE,QAAQ,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAC1D,OAAO,CAAC,IAAI,CAqCd,CAAA"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { lumenDurations, lumenEasings } from './foundations.generated.js';
|
|
2
|
+
const durationMilliseconds = (value) => {
|
|
3
|
+
const parsed = Number.parseFloat(value);
|
|
4
|
+
if (value.endsWith('ms'))
|
|
5
|
+
return parsed;
|
|
6
|
+
if (value.endsWith('s'))
|
|
7
|
+
return parsed * 1000;
|
|
8
|
+
return Number.NaN;
|
|
9
|
+
};
|
|
10
|
+
/** Resolve shared motion tokens without requiring a browser during import. */
|
|
11
|
+
export const getLumenWorkflowTiming = (element, duration = 'standard') => {
|
|
12
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
13
|
+
const token = duration === 'standard' ? '--ui-duration' : `--ui-duration-${duration}`;
|
|
14
|
+
const value = style?.getPropertyValue(token).trim() ?? '';
|
|
15
|
+
const milliseconds = durationMilliseconds(value);
|
|
16
|
+
return {
|
|
17
|
+
duration: Number.isFinite(milliseconds) && milliseconds >= 0 ? milliseconds : lumenDurations[duration],
|
|
18
|
+
easing: style?.getPropertyValue('--ui-ease').trim() || `cubic-bezier(${lumenEasings.standard.join(',')})`
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export const isLumenWorkflowMotionReduced = (element) => Boolean(element.closest('[data-ui-motion="reduce"]')) ||
|
|
22
|
+
Boolean(element.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
23
|
+
const snapshots = (root) => {
|
|
24
|
+
const result = new Map();
|
|
25
|
+
const duplicates = new Set();
|
|
26
|
+
for (const element of root.querySelectorAll(':scope > [data-ui-motion-key]')) {
|
|
27
|
+
const key = element.dataset.uiMotionKey;
|
|
28
|
+
if (!key || element.hidden || duplicates.has(key))
|
|
29
|
+
continue;
|
|
30
|
+
if (result.has(key)) {
|
|
31
|
+
result.delete(key);
|
|
32
|
+
duplicates.add(key);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
result.set(key, { element, rect: element.getBoundingClientRect() });
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
const exitGhost = (root, snapshot) => {
|
|
40
|
+
const descendants = [snapshot.element, ...snapshot.element.querySelectorAll('*')];
|
|
41
|
+
if (descendants.some(element => element.localName.includes('-') || element.matches('iframe, object, embed, audio, video, canvas, script')))
|
|
42
|
+
return undefined;
|
|
43
|
+
const ghost = snapshot.element.cloneNode(true);
|
|
44
|
+
const view = root.ownerDocument.defaultView;
|
|
45
|
+
if (!view || !(ghost instanceof view.HTMLElement))
|
|
46
|
+
return undefined;
|
|
47
|
+
for (const element of [ghost, ...ghost.querySelectorAll('*')]) {
|
|
48
|
+
for (const name of ['id', 'name', 'autofocus', 'data-ui-motion-key'])
|
|
49
|
+
element.removeAttribute(name);
|
|
50
|
+
}
|
|
51
|
+
ghost.setAttribute('aria-hidden', 'true');
|
|
52
|
+
ghost.removeAttribute('hidden');
|
|
53
|
+
ghost.inert = true;
|
|
54
|
+
ghost.dataset.uiMotionGhost = '';
|
|
55
|
+
const frame = root.getBoundingClientRect();
|
|
56
|
+
Object.assign(ghost.style, {
|
|
57
|
+
height: `${snapshot.rect.height}px`,
|
|
58
|
+
left: `${snapshot.rect.left - frame.left + root.scrollLeft}px`,
|
|
59
|
+
margin: '0',
|
|
60
|
+
pointerEvents: 'none',
|
|
61
|
+
position: 'absolute',
|
|
62
|
+
top: `${snapshot.rect.top - frame.top + root.scrollTop}px`,
|
|
63
|
+
width: `${snapshot.rect.width}px`
|
|
64
|
+
});
|
|
65
|
+
root.append(ghost);
|
|
66
|
+
return ghost;
|
|
67
|
+
};
|
|
68
|
+
/** Animate keyed direct children while leaving DOM ordering and focus with the application. */
|
|
69
|
+
export const createLumenMotionGroupController = (root, options = {}) => {
|
|
70
|
+
let previous = snapshots(root);
|
|
71
|
+
let destroyed = false;
|
|
72
|
+
const animations = new Map();
|
|
73
|
+
const preference = root.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)');
|
|
74
|
+
const cancel = () => {
|
|
75
|
+
for (const [animation, cleanup] of animations) {
|
|
76
|
+
animation.cancel();
|
|
77
|
+
cleanup?.();
|
|
78
|
+
}
|
|
79
|
+
animations.clear();
|
|
80
|
+
};
|
|
81
|
+
const animate = (element, keyframes, cleanup) => {
|
|
82
|
+
if (typeof element.animate !== 'function') {
|
|
83
|
+
cleanup?.();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const animation = element.animate(keyframes, getLumenWorkflowTiming(root, options.duration));
|
|
88
|
+
animations.set(animation, cleanup);
|
|
89
|
+
const finish = () => {
|
|
90
|
+
if (!animations.has(animation))
|
|
91
|
+
return;
|
|
92
|
+
animations.delete(animation);
|
|
93
|
+
cleanup?.();
|
|
94
|
+
};
|
|
95
|
+
void animation.finished.then(finish, finish);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
cleanup?.();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const animateChildren = (current) => {
|
|
102
|
+
for (const [key, snapshot] of current) {
|
|
103
|
+
const old = previous.get(key);
|
|
104
|
+
if (old) {
|
|
105
|
+
const x = old.rect.left - snapshot.rect.left;
|
|
106
|
+
const y = old.rect.top - snapshot.rect.top;
|
|
107
|
+
if (x || y)
|
|
108
|
+
animate(snapshot.element, [{ translate: `${x}px ${y}px` }, { translate: '0px 0px' }]);
|
|
109
|
+
}
|
|
110
|
+
else if (options.enterExit !== false) {
|
|
111
|
+
animate(snapshot.element, [{ opacity: 0, translate: '0px 8px' }, { opacity: 1, translate: '0px 0px' }]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const animateExits = (current) => {
|
|
116
|
+
if (options.enterExit === false)
|
|
117
|
+
return;
|
|
118
|
+
for (const [key, snapshot] of previous) {
|
|
119
|
+
if (current.has(key))
|
|
120
|
+
continue;
|
|
121
|
+
const ghost = exitGhost(root, snapshot);
|
|
122
|
+
if (ghost)
|
|
123
|
+
animate(ghost, [{ opacity: 1 }, { opacity: 0 }], () => {
|
|
124
|
+
ghost.remove();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const refresh = () => {
|
|
129
|
+
if (destroyed)
|
|
130
|
+
return;
|
|
131
|
+
const interrupted = animations.size > 0;
|
|
132
|
+
cancel();
|
|
133
|
+
const current = snapshots(root);
|
|
134
|
+
const canAnimate = !interrupted && !isLumenWorkflowMotionReduced(root);
|
|
135
|
+
if (canAnimate && getLumenWorkflowTiming(root, options.duration).duration > 0) {
|
|
136
|
+
animateChildren(current);
|
|
137
|
+
animateExits(current);
|
|
138
|
+
}
|
|
139
|
+
previous = current;
|
|
140
|
+
};
|
|
141
|
+
const reduce = () => {
|
|
142
|
+
if (preference?.matches)
|
|
143
|
+
cancel();
|
|
144
|
+
};
|
|
145
|
+
preference?.addEventListener('change', reduce);
|
|
146
|
+
return {
|
|
147
|
+
destroy: () => {
|
|
148
|
+
destroyed = true;
|
|
149
|
+
cancel();
|
|
150
|
+
preference?.removeEventListener('change', reduce);
|
|
151
|
+
previous.clear();
|
|
152
|
+
},
|
|
153
|
+
refresh,
|
|
154
|
+
update: change => {
|
|
155
|
+
if (destroyed) {
|
|
156
|
+
change();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
previous = snapshots(root);
|
|
160
|
+
const focused = root.ownerDocument.activeElement;
|
|
161
|
+
try {
|
|
162
|
+
change();
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
refresh();
|
|
166
|
+
const view = root.ownerDocument.defaultView;
|
|
167
|
+
if (view && focused instanceof view.HTMLElement && root.contains(focused) &&
|
|
168
|
+
root.ownerDocument.activeElement === root.ownerDocument.body)
|
|
169
|
+
focused.focus({ preventScroll: true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
/** Observe application-owned child changes; animation ghosts never trigger another refresh. */
|
|
175
|
+
export const bindLumenMotionGroup = (root, options = {}) => {
|
|
176
|
+
const controller = createLumenMotionGroupController(root, options);
|
|
177
|
+
const view = root.ownerDocument.defaultView;
|
|
178
|
+
if (!view)
|
|
179
|
+
return () => {
|
|
180
|
+
controller.destroy();
|
|
181
|
+
};
|
|
182
|
+
const isGhost = (node) => node instanceof view.Element && node.hasAttribute('data-ui-motion-ghost');
|
|
183
|
+
const observer = new view.MutationObserver(records => {
|
|
184
|
+
if (records.some(record => record.type === 'attributes' || [...record.addedNodes, ...record.removedNodes].some(node => !isGhost(node))))
|
|
185
|
+
controller.refresh();
|
|
186
|
+
});
|
|
187
|
+
observer.observe(root, { attributeFilter: ['hidden', 'data-ui-motion-key'], attributes: true, childList: true, subtree: true });
|
|
188
|
+
return () => {
|
|
189
|
+
observer.disconnect();
|
|
190
|
+
controller.destroy();
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
/** Execute application updates exactly once, whether a view transition is available or not. */
|
|
194
|
+
export const runLumenViewTransition = async (ownerDocument, update) => {
|
|
195
|
+
const view = ownerDocument.defaultView;
|
|
196
|
+
if (typeof ownerDocument.startViewTransition !== 'function' ||
|
|
197
|
+
view?.matchMedia('(prefers-reduced-motion: reduce)').matches ||
|
|
198
|
+
ownerDocument.documentElement.dataset.uiMotion === 'reduce') {
|
|
199
|
+
await update();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
let pendingUpdate;
|
|
203
|
+
const once = () => {
|
|
204
|
+
pendingUpdate ??= Promise.resolve().then(update);
|
|
205
|
+
return pendingUpdate;
|
|
206
|
+
};
|
|
207
|
+
let transition;
|
|
208
|
+
try {
|
|
209
|
+
transition = ownerDocument.startViewTransition(once);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
await once();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Rendering/animation failures must not suppress a successful application update.
|
|
216
|
+
void transition.ready.catch(() => undefined);
|
|
217
|
+
void transition.finished.catch(() => undefined);
|
|
218
|
+
await transition.updateCallbackDone;
|
|
219
|
+
await transition.finished.catch(() => undefined);
|
|
220
|
+
};
|
package/dist/motion.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type LumenMotionPreset = 'fade' | 'slide-up' | 'scale';
|
|
2
|
+
export type LumenMotionDuration = 'fast' | 'standard' | 'slow';
|
|
3
|
+
export type LumenMotionResult = 'finished' | 'cancelled' | 'skipped';
|
|
4
|
+
export interface LumenPresenceOptions {
|
|
5
|
+
duration?: LumenMotionDuration;
|
|
6
|
+
phase?: 'enter' | 'exit';
|
|
7
|
+
preset?: LumenMotionPreset;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
/** Animate presentation only; consumers retain DOM, focus, and framework state ownership. */
|
|
11
|
+
export declare const animateLumenPresence: (element: HTMLElement, options?: LumenPresenceOptions) => Promise<LumenMotionResult>;
|
|
12
|
+
//# sourceMappingURL=motion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../src/motion.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AAC7D,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAA;AAC9D,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAA;AAEpE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAiGD,6FAA6F;AAC7F,eAAO,MAAM,oBAAoB,GAC/B,SAAS,WAAW,EAAE,UAAS,oBAAyB,KACvD,OAAO,CAAC,iBAAiB,CAqB3B,CAAA"}
|
package/dist/motion.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { lumenDurations, lumenMotion } from './tokens.js';
|
|
2
|
+
const activeAnimations = new WeakMap();
|
|
3
|
+
const readDuration = (style, duration) => {
|
|
4
|
+
const token = duration === 'standard' ? '--ui-duration' : `--ui-duration-${duration}`;
|
|
5
|
+
const match = /^(\d+(?:\.\d+)?|\.\d+)(ms|s)$/.exec(style.getPropertyValue(token).trim());
|
|
6
|
+
if (!match)
|
|
7
|
+
return lumenDurations[duration];
|
|
8
|
+
const value = Number(match[1]) * (match[2] === 's' ? 1000 : 1);
|
|
9
|
+
return Number.isFinite(value) ? value : lumenDurations[duration];
|
|
10
|
+
};
|
|
11
|
+
const presenceFrames = (style, preset) => {
|
|
12
|
+
const transform = style.transform || 'none';
|
|
13
|
+
const baseTransform = transform === 'none' ? '' : `${transform} `;
|
|
14
|
+
let offsetTransform = transform;
|
|
15
|
+
if (preset === 'scale')
|
|
16
|
+
offsetTransform = `${baseTransform}scale(0.96)`;
|
|
17
|
+
if (preset === 'slide-up')
|
|
18
|
+
offsetTransform = `${baseTransform}translateY(0.75rem)`;
|
|
19
|
+
return [
|
|
20
|
+
{ opacity: '0', transform: offsetTransform },
|
|
21
|
+
{ opacity: style.opacity || '1', transform }
|
|
22
|
+
];
|
|
23
|
+
};
|
|
24
|
+
const settleAnimation = async (animation, preference, signal) => {
|
|
25
|
+
const state = { reduced: false };
|
|
26
|
+
const cancel = () => {
|
|
27
|
+
animation.cancel();
|
|
28
|
+
};
|
|
29
|
+
const onPreferenceChange = (event) => {
|
|
30
|
+
if (!event.matches)
|
|
31
|
+
return;
|
|
32
|
+
state.reduced = true;
|
|
33
|
+
cancel();
|
|
34
|
+
};
|
|
35
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
36
|
+
preference.addEventListener('change', onPreferenceChange);
|
|
37
|
+
if (signal?.aborted)
|
|
38
|
+
cancel();
|
|
39
|
+
try {
|
|
40
|
+
await animation.finished;
|
|
41
|
+
return 'finished';
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (animation.playState === 'idle')
|
|
45
|
+
return state.reduced ? 'skipped' : 'cancelled';
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
signal?.removeEventListener('abort', cancel);
|
|
50
|
+
preference.removeEventListener('change', onPreferenceChange);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const motionContext = (element) => {
|
|
54
|
+
const view = element.ownerDocument.defaultView;
|
|
55
|
+
if (!view || !element.isConnected || typeof element.animate !== 'function')
|
|
56
|
+
return undefined;
|
|
57
|
+
if (typeof view.matchMedia !== 'function')
|
|
58
|
+
return undefined;
|
|
59
|
+
const preference = view.matchMedia('(prefers-reduced-motion: reduce)');
|
|
60
|
+
if (preference.matches || element.closest('[data-ui-motion="reduce"]'))
|
|
61
|
+
return undefined;
|
|
62
|
+
return { preference, style: view.getComputedStyle(element) };
|
|
63
|
+
};
|
|
64
|
+
const startPresence = (element, style, options) => {
|
|
65
|
+
const duration = readDuration(style, options.duration ?? 'standard');
|
|
66
|
+
if (duration === 0)
|
|
67
|
+
return undefined;
|
|
68
|
+
const frames = presenceFrames(style, options.preset ?? 'fade');
|
|
69
|
+
if (options.phase === 'exit')
|
|
70
|
+
frames.reverse();
|
|
71
|
+
return element.animate(frames, {
|
|
72
|
+
duration,
|
|
73
|
+
easing: style.getPropertyValue('--ui-ease-emphasized').trim() || lumenMotion.easeEmphasized
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
/** Animate presentation only; consumers retain DOM, focus, and framework state ownership. */
|
|
77
|
+
export const animateLumenPresence = async (element, options = {}) => {
|
|
78
|
+
// An aborted request must not cancel another caller's active animation.
|
|
79
|
+
if (options.signal?.aborted)
|
|
80
|
+
return 'cancelled';
|
|
81
|
+
activeAnimations.get(element)?.cancel();
|
|
82
|
+
const context = motionContext(element);
|
|
83
|
+
if (!context)
|
|
84
|
+
return 'skipped';
|
|
85
|
+
const animation = startPresence(element, context.style, options);
|
|
86
|
+
if (!animation)
|
|
87
|
+
return 'skipped';
|
|
88
|
+
activeAnimations.set(element, animation);
|
|
89
|
+
try {
|
|
90
|
+
return await settleAnimation(animation, context.preference, options.signal);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
if (activeAnimations.get(element) === animation)
|
|
94
|
+
activeAnimations.delete(element);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Bounded, ungrouped decimal drafts. Arithmetic never passes through a floating-point number. */
|
|
2
|
+
export type LumenDecimalDraft = {
|
|
3
|
+
kind: 'empty' | 'incomplete' | 'invalid';
|
|
4
|
+
} | {
|
|
5
|
+
coefficient: bigint;
|
|
6
|
+
kind: 'valid';
|
|
7
|
+
scale: number;
|
|
8
|
+
};
|
|
9
|
+
export interface LumenDecimalOptions {
|
|
10
|
+
locale?: string;
|
|
11
|
+
max?: string;
|
|
12
|
+
min?: string;
|
|
13
|
+
step?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const parseLumenDecimalDraft: (value: string, locale?: string) => LumenDecimalDraft;
|
|
16
|
+
export declare const isLumenDecimalInBounds: (value: string, options?: LumenDecimalOptions) => boolean;
|
|
17
|
+
export declare const stepLumenDecimalDraft: (value: string, direction: -1 | 1, options?: LumenDecimalOptions) => string | null;
|
|
18
|
+
export declare const normalizeLumenNumericOTP: (proposal: string, length?: number) => string | null;
|
|
19
|
+
export interface LumenTimeSelection {
|
|
20
|
+
hour: number;
|
|
21
|
+
minute: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const isLumenTimeSelection: (value: unknown) => value is LumenTimeSelection;
|
|
24
|
+
export declare const isLumenTimeInBounds: (value: unknown, min?: LumenTimeSelection, max?: LumenTimeSelection) => boolean;
|
|
25
|
+
//# sourceMappingURL=native-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-input.d.ts","sourceRoot":"","sources":["../src/native-input.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAAA;CAAE,GAC9C;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAyDD,eAAO,MAAM,sBAAsB,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,KAAG,iBAgBvE,CAAA;AAuCD,eAAO,MAAM,sBAAsB,GAAI,OAAO,MAAM,EAAE,UAAS,mBAAwB,KAAG,OAOzF,CAAA;AAsCD,eAAO,MAAM,qBAAqB,GAChC,OAAO,MAAM,EACb,WAAW,CAAC,CAAC,GAAG,CAAC,EACjB,UAAS,mBAAwB,KAChC,MAAM,GAAG,IAuBX,CAAA;AASD,eAAO,MAAM,wBAAwB,GAAI,UAAU,MAAM,EAAE,eAAU,KAAG,MAAM,GAAG,IAiBhF,CAAA;AAED,MAAM,WAAW,kBAAkB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAMpE,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,kBAG9D,CAAA;AAcD,eAAO,MAAM,mBAAmB,GAC9B,OAAO,OAAO,EACd,MAAM,kBAAkB,EACxB,MAAM,kBAAkB,KACvB,OAMF,CAAA"}
|