@santi020k/lumen-core 3.0.1 → 4.1.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 +384 -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/floating-panel.d.ts +24 -0
- package/dist/floating-panel.d.ts.map +1 -0
- package/dist/floating-panel.js +203 -0
- 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 +37 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -3
- package/dist/loading.d.ts +34 -0
- package/dist/loading.d.ts.map +1 -0
- package/dist/loading.js +70 -0
- 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,34 @@
|
|
|
1
|
+
export declare const lumenSkeletonShapes: readonly ["rectangle", "text", "circle"];
|
|
2
|
+
export type LumenSkeletonShape = (typeof lumenSkeletonShapes)[number];
|
|
3
|
+
export declare const lumenSkeletonAnimations: readonly ["shimmer", "pulse", "none"];
|
|
4
|
+
export type LumenSkeletonAnimation = (typeof lumenSkeletonAnimations)[number];
|
|
5
|
+
export type LumenSkeletonSize = number | string;
|
|
6
|
+
export interface LumenProgressState {
|
|
7
|
+
max: number;
|
|
8
|
+
percentage: number;
|
|
9
|
+
value: number;
|
|
10
|
+
}
|
|
11
|
+
export interface LumenLoadingSemanticsInput {
|
|
12
|
+
ariaHidden?: boolean | string | null | undefined;
|
|
13
|
+
ariaLabel?: string | null | undefined;
|
|
14
|
+
ariaLabelledby?: string | null | undefined;
|
|
15
|
+
role?: string | null | undefined;
|
|
16
|
+
}
|
|
17
|
+
export interface LumenLoadingSemantics {
|
|
18
|
+
ariaHidden: 'true' | undefined;
|
|
19
|
+
role: 'status' | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes progress input to finite ARIA values. Non-finite or non-positive maxima fall back to
|
|
23
|
+
* 100, and missing or non-finite values fall back to 0 before clamping.
|
|
24
|
+
*/
|
|
25
|
+
export declare const normalizeLumenProgress: (max?: unknown, value?: unknown) => LumenProgressState;
|
|
26
|
+
export declare const resolveLumenSkeletonShape: (value: unknown) => LumenSkeletonShape;
|
|
27
|
+
export declare const resolveLumenSkeletonAnimation: (value: unknown) => LumenSkeletonAnimation;
|
|
28
|
+
export declare const resolveLumenSkeletonSize: (value: unknown) => string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Loading indicators are decorative unless named. A named indicator becomes a status region, and
|
|
31
|
+
* explicit `aria-hidden` or `role` values always win.
|
|
32
|
+
*/
|
|
33
|
+
export declare const resolveLumenLoadingSemantics: ({ ariaHidden, ariaLabel, ariaLabelledby, role }: LumenLoadingSemanticsInput) => LumenLoadingSemantics;
|
|
34
|
+
//# sourceMappingURL=loading.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loading.d.ts","sourceRoot":"","sources":["../src/loading.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,0CAA2C,CAAA;AAE3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAErE,eAAO,MAAM,uBAAuB,uCAAwC,CAAA;AAE5E,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7E,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAA;AAE/C,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACrC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAC1C,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAA;CAC3B;AAeD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,OAAO,EAAE,QAAQ,OAAO,KAAG,kBAMvE,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAI,OAAO,OAAO,KAAG,kBAE1D,CAAA;AAED,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,sBAE9D,CAAA;AA6BD,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAYlE,CAAA;AAKD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GAAI,iDAK1C,0BAA0B,KAAG,qBAS/B,CAAA"}
|
package/dist/loading.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const lumenSkeletonShapes = ['rectangle', 'text', 'circle'];
|
|
2
|
+
export const lumenSkeletonAnimations = ['shimmer', 'pulse', 'none'];
|
|
3
|
+
const defaultProgressMax = 100;
|
|
4
|
+
const maxSizeLength = 64;
|
|
5
|
+
const toFiniteNumber = (value) => {
|
|
6
|
+
if (typeof value === 'number')
|
|
7
|
+
return Number.isFinite(value) ? value : undefined;
|
|
8
|
+
if (typeof value !== 'string' || !value.trim())
|
|
9
|
+
return undefined;
|
|
10
|
+
const parsed = Number(value);
|
|
11
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Normalizes progress input to finite ARIA values. Non-finite or non-positive maxima fall back to
|
|
15
|
+
* 100, and missing or non-finite values fall back to 0 before clamping.
|
|
16
|
+
*/
|
|
17
|
+
export const normalizeLumenProgress = (max, value) => {
|
|
18
|
+
const finiteMax = toFiniteNumber(max);
|
|
19
|
+
const safeMax = finiteMax !== undefined && finiteMax > 0 ? finiteMax : defaultProgressMax;
|
|
20
|
+
const safeValue = Math.min(safeMax, Math.max(0, toFiniteNumber(value) ?? 0));
|
|
21
|
+
return { max: safeMax, percentage: (safeValue / safeMax) * 100, value: safeValue };
|
|
22
|
+
};
|
|
23
|
+
export const resolveLumenSkeletonShape = (value) => (lumenSkeletonShapes.find(shape => shape === value) ?? 'rectangle');
|
|
24
|
+
export const resolveLumenSkeletonAnimation = (value) => (lumenSkeletonAnimations.find(animation => animation === value) ?? 'shimmer');
|
|
25
|
+
// Letters keep their case because custom property names in var() are case-sensitive.
|
|
26
|
+
const isSafeSizeCharacter = (character) => ((character >= 'a' && character <= 'z') ||
|
|
27
|
+
(character >= 'A' && character <= 'Z') ||
|
|
28
|
+
(character >= '0' && character <= '9') ||
|
|
29
|
+
'%.+-*/(), _'.includes(character));
|
|
30
|
+
/**
|
|
31
|
+
* Resolves a Skeleton width or height to a CSS length. Finite non-negative numbers become pixels;
|
|
32
|
+
* strings may be lengths, percentages, or math and `var()` expressions, limited to ASCII letters,
|
|
33
|
+
* digits, and math characters so they cannot add declarations.
|
|
34
|
+
*/
|
|
35
|
+
const toPixelSize = (value) => (Number.isFinite(value) && value >= 0 ? `${value}px` : undefined);
|
|
36
|
+
const isSafeSizeExpression = (size) => {
|
|
37
|
+
if (!size || size.length > maxSizeLength)
|
|
38
|
+
return false;
|
|
39
|
+
for (const character of size) {
|
|
40
|
+
if (!isSafeSizeCharacter(character))
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
export const resolveLumenSkeletonSize = (value) => {
|
|
46
|
+
if (typeof value === 'number')
|
|
47
|
+
return toPixelSize(value);
|
|
48
|
+
if (typeof value !== 'string')
|
|
49
|
+
return undefined;
|
|
50
|
+
const size = value.trim();
|
|
51
|
+
if (!isSafeSizeExpression(size))
|
|
52
|
+
return undefined;
|
|
53
|
+
const pixels = toFiniteNumber(size);
|
|
54
|
+
return pixels === undefined ? size : toPixelSize(pixels);
|
|
55
|
+
};
|
|
56
|
+
// Names come from untyped markup and props at runtime, so only non-blank strings count.
|
|
57
|
+
const hasAccessibleNameText = (value) => typeof value === 'string' && value.trim() !== '';
|
|
58
|
+
/**
|
|
59
|
+
* Loading indicators are decorative unless named. A named indicator becomes a status region, and
|
|
60
|
+
* explicit `aria-hidden` or `role` values always win.
|
|
61
|
+
*/
|
|
62
|
+
export const resolveLumenLoadingSemantics = ({ ariaHidden, ariaLabel, ariaLabelledby, role }) => {
|
|
63
|
+
const isNamed = hasAccessibleNameText(ariaLabel) || hasAccessibleNameText(ariaLabelledby);
|
|
64
|
+
const hasExplicitHidden = ariaHidden !== undefined && ariaHidden !== null;
|
|
65
|
+
const hasExplicitRole = role !== undefined && role !== null;
|
|
66
|
+
return {
|
|
67
|
+
ariaHidden: hasExplicitHidden || isNamed ? undefined : 'true',
|
|
68
|
+
role: hasExplicitRole || !isNamed ? undefined : 'status'
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type LumenMediaViewportAction, type LumenMediaViewportValue } from './media-workspace.js';
|
|
2
|
+
export interface LumenMediaViewportLabels {
|
|
3
|
+
down: string;
|
|
4
|
+
fit: string;
|
|
5
|
+
left: string;
|
|
6
|
+
right: string;
|
|
7
|
+
up: string;
|
|
8
|
+
'zoom-in': string;
|
|
9
|
+
'zoom-out': string;
|
|
10
|
+
}
|
|
11
|
+
export declare const lumenMediaViewportLabels: LumenMediaViewportLabels;
|
|
12
|
+
export declare const lumenMediaViewportActions: readonly ["zoom-in", "zoom-out", "fit", "left", "right", "up", "down"];
|
|
13
|
+
export declare const isLumenMediaViewportAction: (value: unknown) => value is LumenMediaViewportAction;
|
|
14
|
+
export declare const formatLumenMediaZoom: (zoom: number, locale?: string) => string;
|
|
15
|
+
/** Shared pointer/keyboard interaction; the consumer decides whether to accept each requested value. */
|
|
16
|
+
export declare const bindLumenMediaViewport: (root: HTMLElement, options: {
|
|
17
|
+
disabled: () => boolean;
|
|
18
|
+
getValue: () => LumenMediaViewportValue;
|
|
19
|
+
maxZoom: () => number;
|
|
20
|
+
onValueChange: (value: LumenMediaViewportValue) => void;
|
|
21
|
+
}) => (() => void);
|
|
22
|
+
/** Writes only Lumen-owned styles and controls; image loading and contents stay application-owned. */
|
|
23
|
+
export declare const syncLumenMediaViewport: (root: HTMLElement, value: LumenMediaViewportValue, maxZoom?: number, disabled?: boolean, locale?: string) => void;
|
|
24
|
+
//# sourceMappingURL=media-viewport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-viewport.d.ts","sourceRoot":"","sources":["../src/media-viewport.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAG7B,MAAM,sBAAsB,CAAA;AAE7B,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,wBAAwB,EAAE,wBAQtC,CAAA;AAED,eAAO,MAAM,yBAAyB,wEAAyE,CAAA;AAE/G,eAAO,MAAM,0BAA0B,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,wBAEpE,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,MAMpE,CAAA;AAUD,wGAAwG;AACxG,eAAO,MAAM,sBAAsB,GACjC,MAAM,WAAW,EACjB,SAAS;IACP,QAAQ,EAAE,MAAM,OAAO,CAAA;IACvB,QAAQ,EAAE,MAAM,uBAAuB,CAAA;IACvC,OAAO,EAAE,MAAM,MAAM,CAAA;IACrB,aAAa,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAA;CACxD,KACA,CAAC,MAAM,IAAI,CAwFb,CAAA;AAQD,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,GACjC,MAAM,WAAW,EAAE,OAAO,uBAAuB,EAAE,gBAAW,EAAE,kBAAgB,EAAE,SAAS,MAAM,KAChG,IAwBF,CAAA"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { applyLumenMediaViewportAction, normalizeLumenMediaViewport, panLumenMediaViewport } from './media-workspace.js';
|
|
2
|
+
export const lumenMediaViewportLabels = {
|
|
3
|
+
down: 'Pan down',
|
|
4
|
+
fit: 'Fit to view',
|
|
5
|
+
left: 'Pan left',
|
|
6
|
+
right: 'Pan right',
|
|
7
|
+
up: 'Pan up',
|
|
8
|
+
'zoom-in': 'Zoom in',
|
|
9
|
+
'zoom-out': 'Zoom out'
|
|
10
|
+
};
|
|
11
|
+
export const lumenMediaViewportActions = ['zoom-in', 'zoom-out', 'fit', 'left', 'right', 'up', 'down'];
|
|
12
|
+
export const isLumenMediaViewportAction = (value) => (value === 'zoom-in' || value === 'zoom-out' || value === 'fit' || value === 'left' || value === 'right' || value === 'up' || value === 'down');
|
|
13
|
+
export const formatLumenMediaZoom = (zoom, locale) => {
|
|
14
|
+
try {
|
|
15
|
+
return new Intl.NumberFormat(locale, { style: 'percent', maximumFractionDigits: 0 }).format(zoom);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return new Intl.NumberFormat('en', { style: 'percent', maximumFractionDigits: 0 }).format(zoom);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const closestViewportElement = (target, selector) => {
|
|
22
|
+
try {
|
|
23
|
+
return Element.prototype.closest.call(target, selector);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/** Shared pointer/keyboard interaction; the consumer decides whether to accept each requested value. */
|
|
30
|
+
export const bindLumenMediaViewport = (root, options) => {
|
|
31
|
+
const stage = root.querySelector('[data-ui-media-viewport-stage]');
|
|
32
|
+
if (!stage)
|
|
33
|
+
return () => undefined;
|
|
34
|
+
const view = root.ownerDocument.defaultView;
|
|
35
|
+
const abort = new (view?.AbortController ?? AbortController)();
|
|
36
|
+
const { signal } = abort;
|
|
37
|
+
let drag;
|
|
38
|
+
const act = (action) => {
|
|
39
|
+
if (options.disabled())
|
|
40
|
+
return;
|
|
41
|
+
options.onValueChange(applyLumenMediaViewportAction(options.getValue(), action, options.maxZoom()));
|
|
42
|
+
};
|
|
43
|
+
root.addEventListener('click', event => {
|
|
44
|
+
const button = closestViewportElement(event.target, 'button[data-ui-media-viewport-action]');
|
|
45
|
+
const action = button?.getAttribute('data-ui-media-viewport-action');
|
|
46
|
+
if (!button || button.hasAttribute('disabled') || !root.contains(button) || !isLumenMediaViewportAction(action))
|
|
47
|
+
return;
|
|
48
|
+
act(action);
|
|
49
|
+
}, { signal });
|
|
50
|
+
const keys = {
|
|
51
|
+
ArrowDown: 'down', ArrowLeft: 'left', ArrowRight: 'right', ArrowUp: 'up', Home: 'fit', '+': 'zoom-in', '=': 'zoom-in', '-': 'zoom-out'
|
|
52
|
+
};
|
|
53
|
+
stage.addEventListener('keydown', event => {
|
|
54
|
+
if (event.target !== stage || event.altKey || event.ctrlKey || event.metaKey || options.disabled())
|
|
55
|
+
return;
|
|
56
|
+
const action = keys[event.key];
|
|
57
|
+
if (!action)
|
|
58
|
+
return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
act(action);
|
|
61
|
+
}, { signal });
|
|
62
|
+
stage.addEventListener('pointerdown', event => {
|
|
63
|
+
if (options.disabled() || drag || event.button !== 0 || options.getValue().zoom <= 1)
|
|
64
|
+
return;
|
|
65
|
+
if (closestViewportElement(event.target, 'button, input, a, select, textarea, [contenteditable]'))
|
|
66
|
+
return;
|
|
67
|
+
drag = { id: event.pointerId, x: event.clientX, y: event.clientY, value: options.getValue() };
|
|
68
|
+
stage.setPointerCapture(event.pointerId);
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
}, { signal });
|
|
71
|
+
stage.addEventListener('pointermove', event => {
|
|
72
|
+
if (drag?.id !== event.pointerId || options.disabled())
|
|
73
|
+
return;
|
|
74
|
+
const bounds = stage.getBoundingClientRect();
|
|
75
|
+
options.onValueChange(panLumenMediaViewport(drag.value, event.clientX - drag.x, event.clientY - drag.y, bounds.width, bounds.height, options.maxZoom()));
|
|
76
|
+
}, { signal });
|
|
77
|
+
const stopDrag = (event) => {
|
|
78
|
+
if (drag?.id !== event.pointerId)
|
|
79
|
+
return;
|
|
80
|
+
drag = undefined;
|
|
81
|
+
if (stage.hasPointerCapture(event.pointerId))
|
|
82
|
+
stage.releasePointerCapture(event.pointerId);
|
|
83
|
+
};
|
|
84
|
+
stage.addEventListener('pointerup', stopDrag, { signal });
|
|
85
|
+
stage.addEventListener('pointercancel', stopDrag, { signal });
|
|
86
|
+
stage.addEventListener('lostpointercapture', stopDrag, { signal });
|
|
87
|
+
stage.addEventListener('dragstart', event => {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
}, { signal });
|
|
90
|
+
return () => {
|
|
91
|
+
if (drag && stage.hasPointerCapture(drag.id))
|
|
92
|
+
stage.releasePointerCapture(drag.id);
|
|
93
|
+
drag = undefined;
|
|
94
|
+
abort.abort();
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
const viewportTouchAction = (zoom, disabled) => zoom > 1 && !disabled ? 'none' : 'pan-y';
|
|
98
|
+
const sameViewportValue = (left, right) => (left.zoom === right.zoom && left.x === right.x && left.y === right.y);
|
|
99
|
+
/** Writes only Lumen-owned styles and controls; image loading and contents stay application-owned. */
|
|
100
|
+
export const syncLumenMediaViewport = (root, value, maxZoom = 4, disabled = false, locale) => {
|
|
101
|
+
const current = normalizeLumenMediaViewport(value, maxZoom);
|
|
102
|
+
const content = root.querySelector('[data-ui-media-viewport-content]');
|
|
103
|
+
const stage = root.querySelector('[data-ui-media-viewport-stage]');
|
|
104
|
+
const status = root.querySelector('[data-ui-media-viewport-status]');
|
|
105
|
+
if (content) {
|
|
106
|
+
content.style.transform = `translate(${current.x * (current.zoom - 1) * 50}%, ${current.y * (current.zoom - 1) * 50}%) scale(${current.zoom})`;
|
|
107
|
+
}
|
|
108
|
+
if (stage) {
|
|
109
|
+
stage.style.touchAction = viewportTouchAction(current.zoom, disabled);
|
|
110
|
+
stage.setAttribute('aria-disabled', String(disabled));
|
|
111
|
+
}
|
|
112
|
+
if (status)
|
|
113
|
+
status.textContent = formatLumenMediaZoom(current.zoom, locale);
|
|
114
|
+
for (const button of root.querySelectorAll('button[data-ui-media-viewport-action]')) {
|
|
115
|
+
const action = button.dataset.uiMediaViewportAction;
|
|
116
|
+
const next = isLumenMediaViewportAction(action) ? applyLumenMediaViewportAction(current, action, maxZoom) : current;
|
|
117
|
+
button.disabled = disabled || sameViewportValue(next, current);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Media content, loading and persistence remain owned by each framework's consumer. */
|
|
2
|
+
export interface LumenMediaIdentity {
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
id: string;
|
|
5
|
+
}
|
|
6
|
+
/** Returns a new collection; stale requests and out-of-range destinations leave its order intact. */
|
|
7
|
+
export declare const moveLumenMediaItem: <Item extends LumenMediaIdentity>(items: readonly Item[], id: string, targetIndex: number) => Item[];
|
|
8
|
+
/** Prunes missing IDs and duplicates, preserving the displayed media order. */
|
|
9
|
+
export declare const resolveLumenMediaSelection: (items: readonly LumenMediaIdentity[], selectedIds: readonly string[]) => string[];
|
|
10
|
+
export declare const toggleLumenMediaSelection: (items: readonly LumenMediaIdentity[], selectedIds: readonly string[], id: string) => string[];
|
|
11
|
+
/** Zoom relative to fit; x/y are fractions of the available pan extent, from -1 to 1. */
|
|
12
|
+
export interface LumenMediaViewportValue {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
zoom: number;
|
|
16
|
+
}
|
|
17
|
+
export type LumenMediaViewportAction = 'zoom-in' | 'zoom-out' | 'left' | 'right' | 'up' | 'down' | 'fit';
|
|
18
|
+
export declare const resolveLumenMediaMaxZoom: (maxZoom?: number) => number;
|
|
19
|
+
export declare const normalizeLumenMediaViewport: (value?: unknown, maxZoom?: number) => LumenMediaViewportValue;
|
|
20
|
+
export declare const applyLumenMediaViewportAction: (value: LumenMediaViewportValue, action: LumenMediaViewportAction, maxZoom?: number) => LumenMediaViewportValue;
|
|
21
|
+
/** Maps pointer movement to bounded pan coordinates without allowing media outside its viewport. */
|
|
22
|
+
export declare const panLumenMediaViewport: (value: LumenMediaViewportValue, deltaX: number, deltaY: number, width: number, height: number, maxZoom?: number) => LumenMediaViewportValue;
|
|
23
|
+
export type LumenMediaThumbnailState = 'ready' | 'loading' | 'error';
|
|
24
|
+
export declare const resolveLumenMediaThumbnailState: (state: unknown) => LumenMediaThumbnailState;
|
|
25
|
+
export declare const resolveLumenMediaOrder: (order?: number) => number | undefined;
|
|
26
|
+
export declare const formatLumenMediaThumbnailState: (state: LumenMediaThumbnailState, label?: string) => string;
|
|
27
|
+
//# sourceMappingURL=media-workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-workspace.d.ts","sourceRoot":"","sources":["../src/media-workspace.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;CACX;AAYD,qGAAqG;AACrG,eAAO,MAAM,kBAAkB,GAAI,IAAI,SAAS,kBAAkB,EAChE,OAAO,SAAS,IAAI,EAAE,EAAE,IAAI,MAAM,EAAE,aAAa,MAAM,KACtD,IAAI,EAeN,CAAA;AAED,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,GACrC,OAAO,SAAS,kBAAkB,EAAE,EAAE,aAAa,SAAS,MAAM,EAAE,KACnE,MAAM,EAMR,CAAA;AAED,eAAO,MAAM,yBAAyB,GACpC,OAAO,SAAS,kBAAkB,EAAE,EAAE,aAAa,SAAS,MAAM,EAAE,EAAE,IAAI,MAAM,KAC/E,MAAM,EAUR,CAAA;AAED,yFAAyF;AACzF,MAAM,WAAW,uBAAuB;IACtC,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,CAAA;AAExG,eAAO,MAAM,wBAAwB,GAAI,gBAAW,KAAG,MAEtD,CAAA;AAMD,eAAO,MAAM,2BAA2B,GACtC,QAAO,OAAY,EAAE,gBAAW,KAC/B,uBASF,CAAA;AAED,eAAO,MAAM,6BAA6B,GACxC,OAAO,uBAAuB,EAAE,QAAQ,wBAAwB,EAAE,gBAAW,KAC5E,uBAkBF,CAAA;AAED,oGAAoG;AACpG,eAAO,MAAM,qBAAqB,GAChC,OAAO,uBAAuB,EAAE,QAAQ,MAAM,EAAE,QAAQ,MAAM,EAC9D,OAAO,MAAM,EAAE,QAAQ,MAAM,EAAE,gBAAW,KACzC,uBAWF,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAA;AAEpE,eAAO,MAAM,+BAA+B,GAAI,OAAO,OAAO,KAAG,wBAEhE,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,SAEhE,CAAA;AAED,eAAO,MAAM,8BAA8B,GAAI,OAAO,wBAAwB,EAAE,QAAQ,MAAM,KAAG,MAIhG,CAAA"}
|
|
@@ -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"}
|