@santi020k/lumen-core 3.0.0 → 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.
Files changed (147) hide show
  1. package/PHONE_FLAG_LICENSE.txt +21 -0
  2. package/README.md +379 -4
  3. package/THIRD_PARTY_NOTICES.md +16 -2
  4. package/dist/ai-surfaces.d.ts +25 -0
  5. package/dist/ai-surfaces.d.ts.map +1 -0
  6. package/dist/ai-surfaces.js +162 -0
  7. package/dist/amount-field.d.ts +22 -0
  8. package/dist/amount-field.d.ts.map +1 -0
  9. package/dist/amount-field.js +207 -0
  10. package/dist/attachments.d.ts +15 -0
  11. package/dist/attachments.d.ts.map +1 -0
  12. package/dist/attachments.js +123 -0
  13. package/dist/bullet-chart.d.ts +33 -0
  14. package/dist/bullet-chart.d.ts.map +1 -0
  15. package/dist/bullet-chart.js +80 -0
  16. package/dist/chart-activation.d.ts +32 -0
  17. package/dist/chart-activation.d.ts.map +1 -0
  18. package/dist/chart-activation.js +97 -0
  19. package/dist/chart-interaction.d.ts +12 -0
  20. package/dist/chart-interaction.d.ts.map +1 -0
  21. package/dist/chart-interaction.js +274 -0
  22. package/dist/chart-models.d.ts +156 -0
  23. package/dist/chart-models.d.ts.map +1 -0
  24. package/dist/chart-models.js +348 -0
  25. package/dist/chart-motion.d.ts +6 -0
  26. package/dist/chart-motion.d.ts.map +1 -0
  27. package/dist/chart-motion.js +107 -0
  28. package/dist/charts.d.ts +68 -3
  29. package/dist/charts.d.ts.map +1 -1
  30. package/dist/charts.js +328 -29
  31. package/dist/code.js +1 -1
  32. package/dist/combobox.d.ts +7 -0
  33. package/dist/combobox.d.ts.map +1 -0
  34. package/dist/combobox.js +253 -0
  35. package/dist/comparison-chart.d.ts +32 -0
  36. package/dist/comparison-chart.d.ts.map +1 -0
  37. package/dist/comparison-chart.js +64 -0
  38. package/dist/components.d.ts +210 -25
  39. package/dist/components.d.ts.map +1 -1
  40. package/dist/components.js +102 -26
  41. package/dist/dashboard.d.ts +16 -0
  42. package/dist/dashboard.d.ts.map +1 -0
  43. package/dist/dashboard.js +21 -0
  44. package/dist/data.d.ts.map +1 -1
  45. package/dist/data.js +21 -5
  46. package/dist/dates.d.ts +13 -0
  47. package/dist/dates.d.ts.map +1 -0
  48. package/dist/dates.js +37 -0
  49. package/dist/device-frame.d.ts +25 -0
  50. package/dist/device-frame.d.ts.map +1 -0
  51. package/dist/device-frame.js +50 -0
  52. package/dist/direction.d.ts +3 -0
  53. package/dist/direction.d.ts.map +1 -0
  54. package/dist/direction.js +10 -0
  55. package/dist/extended-charts.d.ts +95 -0
  56. package/dist/extended-charts.d.ts.map +1 -0
  57. package/dist/extended-charts.js +175 -0
  58. package/dist/figma.d.ts.map +1 -1
  59. package/dist/figma.js +10 -1
  60. package/dist/forms.d.ts.map +1 -1
  61. package/dist/forms.js +18 -4
  62. package/dist/icon-data.generated.d.ts +1 -1
  63. package/dist/icon-data.generated.d.ts.map +1 -1
  64. package/dist/icon-data.generated.js +1 -1
  65. package/dist/icons.js +3 -4
  66. package/dist/illustrations.generated.d.ts +1 -1
  67. package/dist/image-comparison.d.ts +14 -0
  68. package/dist/image-comparison.d.ts.map +1 -0
  69. package/dist/image-comparison.js +48 -0
  70. package/dist/index.d.ts +35 -4
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +34 -3
  73. package/dist/media-viewport.d.ts +24 -0
  74. package/dist/media-viewport.d.ts.map +1 -0
  75. package/dist/media-viewport.js +119 -0
  76. package/dist/media-workspace.d.ts +27 -0
  77. package/dist/media-workspace.d.ts.map +1 -0
  78. package/dist/media-workspace.js +83 -0
  79. package/dist/message-scroller.d.ts +14 -0
  80. package/dist/message-scroller.d.ts.map +1 -0
  81. package/dist/message-scroller.js +113 -0
  82. package/dist/motion-workflows.d.ts +24 -0
  83. package/dist/motion-workflows.d.ts.map +1 -0
  84. package/dist/motion-workflows.js +220 -0
  85. package/dist/motion.d.ts +12 -0
  86. package/dist/motion.d.ts.map +1 -0
  87. package/dist/motion.js +96 -0
  88. package/dist/native-input.d.ts +25 -0
  89. package/dist/native-input.d.ts.map +1 -0
  90. package/dist/native-input.js +188 -0
  91. package/dist/phone-flags.generated.d.ts +2 -0
  92. package/dist/phone-flags.generated.d.ts.map +1 -0
  93. package/dist/phone-flags.generated.js +263 -0
  94. package/dist/phone.d.ts +4 -0
  95. package/dist/phone.d.ts.map +1 -1
  96. package/dist/phone.js +6 -1
  97. package/dist/rich-text.d.ts +7 -0
  98. package/dist/rich-text.d.ts.map +1 -1
  99. package/dist/rich-text.js +13 -0
  100. package/dist/rive.d.ts +37 -0
  101. package/dist/rive.d.ts.map +1 -0
  102. package/dist/rive.js +129 -0
  103. package/dist/schedule.d.ts.map +1 -1
  104. package/dist/schedule.js +63 -23
  105. package/dist/tab-indicator.d.ts +3 -0
  106. package/dist/tab-indicator.d.ts.map +1 -0
  107. package/dist/tab-indicator.js +57 -0
  108. package/dist/theme-audit.d.ts +16 -0
  109. package/dist/theme-audit.d.ts.map +1 -0
  110. package/dist/theme-audit.js +57 -0
  111. package/dist/theme-builder.d.ts +7 -1
  112. package/dist/theme-builder.d.ts.map +1 -1
  113. package/dist/theme-builder.js +45 -23
  114. package/dist/theme-presets.generated.d.ts +217 -0
  115. package/dist/theme-presets.generated.d.ts.map +1 -0
  116. package/dist/theme-presets.generated.js +231 -0
  117. package/dist/theme.d.ts +11 -3
  118. package/dist/theme.d.ts.map +1 -1
  119. package/dist/theme.js +56 -6
  120. package/dist/virtual-collection.d.ts +14 -0
  121. package/dist/virtual-collection.d.ts.map +1 -0
  122. package/dist/virtual-collection.js +132 -0
  123. package/dist/virtual-list.d.ts +7 -0
  124. package/dist/virtual-list.d.ts.map +1 -0
  125. package/dist/virtual-list.js +135 -0
  126. package/dist/virtual-window.d.ts +19 -0
  127. package/dist/virtual-window.d.ts.map +1 -0
  128. package/dist/virtual-window.js +84 -0
  129. package/dist/visual-effects.d.ts +6 -0
  130. package/dist/visual-effects.d.ts.map +1 -0
  131. package/dist/visual-effects.js +44 -0
  132. package/dist/world-map-data.generated.d.ts +9 -0
  133. package/dist/world-map-data.generated.d.ts.map +1 -0
  134. package/dist/world-map-data.generated.js +240 -0
  135. package/dist/world-map-navigation.d.ts +16 -0
  136. package/dist/world-map-navigation.d.ts.map +1 -0
  137. package/dist/world-map-navigation.js +53 -0
  138. package/dist/world-map-pan.d.ts +3 -0
  139. package/dist/world-map-pan.d.ts.map +1 -0
  140. package/dist/world-map-pan.js +66 -0
  141. package/dist/world-map-zoom.d.ts +13 -0
  142. package/dist/world-map-zoom.d.ts.map +1 -0
  143. package/dist/world-map-zoom.js +61 -0
  144. package/dist/world-map.d.ts +71 -0
  145. package/dist/world-map.d.ts.map +1 -0
  146. package/dist/world-map.js +143 -0
  147. package/package.json +51 -4
package/dist/theme.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { lumenCssColorTokens, lumenRadii, lumenSpacing } from './foundations.generated.js';
2
+ import { lumenThemePresetDefinitions } from './theme-presets.generated.js';
3
+ export { lumenThemePresetDefinitions } from './theme-presets.generated.js';
1
4
  export const lumenSemanticColorTokenNames = [
2
5
  'canvas',
3
6
  'surface',
@@ -73,7 +76,16 @@ export const lumenStructureTokenNames = [
73
76
  ...lumenRadiusTokenNames,
74
77
  ...lumenShadowTokenNames,
75
78
  ...lumenTypographyTokenNames,
76
- ...lumenMotionTokenNames
79
+ ...lumenMotionTokenNames,
80
+ 'ui-border-width',
81
+ 'ui-space-zero',
82
+ 'ui-space-xs',
83
+ 'ui-space-sm',
84
+ 'ui-space-md',
85
+ 'ui-space-lg',
86
+ 'ui-space-xl',
87
+ 'ui-space-2xl',
88
+ 'ui-space-3xl'
77
89
  ];
78
90
  export const lumenColorTokenNames = [
79
91
  ...lumenSemanticColorTokenNames,
@@ -192,8 +204,50 @@ const createStructureTokens = (hue, scheme = 'light') => ({
192
204
  ...lumenRadiusTokens,
193
205
  ...createShadowTokens(hue, scheme),
194
206
  ...lumenTypographyTokens,
195
- ...lumenMotionTokens
207
+ ...lumenMotionTokens,
208
+ 'ui-border-width': '1px',
209
+ 'ui-space-zero': '0rem',
210
+ 'ui-space-xs': `${lumenSpacing.xs / 16}rem`,
211
+ 'ui-space-sm': `${lumenSpacing.sm / 16}rem`,
212
+ 'ui-space-md': `${lumenSpacing.md / 16}rem`,
213
+ 'ui-space-lg': `${lumenSpacing.lg / 16}rem`,
214
+ 'ui-space-xl': `${lumenSpacing.xl / 16}rem`,
215
+ 'ui-space-2xl': `${lumenSpacing['2xl'] / 16}rem`,
216
+ 'ui-space-3xl': `${lumenSpacing['3xl'] / 16}rem`
196
217
  });
218
+ export const mergeThemeTokens = (tokens, overrides) => ({
219
+ ...tokens,
220
+ ...Object.fromEntries(Object.entries(overrides).filter((entry) => Boolean(entry[1])))
221
+ });
222
+ /** A preset configures tokens; components retain explicit control of glass surfaces. */
223
+ export const createThemePreset = (preset = 'default', options = {}) => {
224
+ const scheme = options.scheme ?? 'light';
225
+ const definition = lumenThemePresetDefinitions[preset];
226
+ const colors = Object.fromEntries(lumenSemanticColorTokenNames.map(name => [
227
+ name,
228
+ Object.entries(lumenCssColorTokens[scheme]).find(([key]) => key === name.replaceAll(/-([a-z])/g, (_match, character) => character.toUpperCase()))?.[1] ?? ''
229
+ ]));
230
+ const tokens = {
231
+ ...colors,
232
+ ...definition.cssColors[scheme],
233
+ ...createGlassThemeTokens(preset === 'studio' ? 0 : 220, scheme),
234
+ ...createStructureTokens(222, scheme),
235
+ ...definition.effects[scheme],
236
+ 'ui-border-width': `${definition.borderWidth}px`,
237
+ 'ui-font': definition.fontFamily.map(family => ['system-ui', 'sans-serif'].includes(family) ? family : `"${family}"`).join(', '),
238
+ 'ui-radius-sm': `${lumenRadii.sm / 16 * definition.radiusScale}rem`,
239
+ 'ui-radius': `${lumenRadii.md / 16 * definition.radiusScale}rem`,
240
+ 'ui-radius-lg': `${lumenRadii.lg / 16 * definition.radiusScale}rem`,
241
+ ...Object.fromEntries(Object.entries(lumenSpacing).map(([name, value]) => [
242
+ `ui-space-${name}`, `${value * definition.spacingScale / 16}rem`
243
+ ]))
244
+ };
245
+ if (definition.elevationScale === 0) {
246
+ for (const name of lumenShadowTokenNames)
247
+ tokens[name] = 'none';
248
+ }
249
+ return mergeThemeTokens(tokens, options.overrides ?? {});
250
+ };
197
251
  export const createThemePalette = (brand, accent = brand) => ({
198
252
  accent,
199
253
  brand,
@@ -326,10 +380,6 @@ export const scoreThemeContrast = (tokens, foreground = 'ink', background = 'can
326
380
  wcagAAA: ratio >= 7
327
381
  };
328
382
  };
329
- export const mergeThemeTokens = (tokens, overrides) => ({
330
- ...tokens,
331
- ...Object.fromEntries(Object.entries(overrides).filter((entry) => Boolean(entry[1])))
332
- });
333
383
  export const suggestReadableInk = (background) => {
334
384
  const darkForeground = '0 0% 0%';
335
385
  const lightForeground = '0 0% 100%';
@@ -0,0 +1,14 @@
1
+ export interface LumenVirtualCollectionOptions<T> {
2
+ getKey: (item: T, index: number) => string | number;
3
+ items: readonly T[];
4
+ itemSize?: number;
5
+ overscan?: number;
6
+ renderItem: (item: T, index: number, previous?: HTMLElement) => HTMLElement;
7
+ }
8
+ export interface LumenVirtualCollectionController<T> {
9
+ destroy: () => void;
10
+ update: (items: readonly T[]) => void;
11
+ }
12
+ /** Data mode owns an empty VirtualList root; only its visible window and focused neighbors mount. */
13
+ export declare const createLumenVirtualCollectionController: <T>(root: HTMLElement, options: LumenVirtualCollectionOptions<T>) => LumenVirtualCollectionController<T>;
14
+ //# sourceMappingURL=virtual-collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-collection.d.ts","sourceRoot":"","sources":["../src/virtual-collection.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,6BAA6B,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;IACnD,KAAK,EAAE,SAAS,CAAC,EAAE,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,KAAK,WAAW,CAAA;CAC5E;AACD,MAAM,WAAW,gCAAgC,CAAC,CAAC;IACjD,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,IAAI,CAAA;CACtC;AA0CD,qGAAqG;AACrG,eAAO,MAAM,sCAAsC,GAAI,CAAC,EACtD,MAAM,WAAW,EACjB,SAAS,6BAA6B,CAAC,CAAC,CAAC,KACxC,gCAAgC,CAAC,CAAC,CA2IpC,CAAA"}
@@ -0,0 +1,132 @@
1
+ import { observeLumenVirtualWindow } from './virtual-window.js';
2
+ const isHtmlElement = (element) => element?.namespaceURI === 'http://www.w3.org/1999/xhtml';
3
+ const positionRow = (root, row, index, count, itemSize) => {
4
+ if (root.getAttribute('role') === 'list') {
5
+ row.setAttribute('role', 'listitem');
6
+ row.setAttribute('aria-posinset', String(index + 1));
7
+ row.setAttribute('aria-setsize', String(count));
8
+ }
9
+ row.dataset.uiVirtualListIndex = String(index);
10
+ row.style.position = 'absolute';
11
+ row.style.insetInline = '0';
12
+ row.style.top = `${index * itemSize}px`;
13
+ row.style.height = `${itemSize}px`;
14
+ row.style.boxSizing = 'border-box';
15
+ };
16
+ const insertRow = (content, row, previousRow) => {
17
+ if (row.previousElementSibling !== (previousRow ?? null) || row.parentElement !== content) {
18
+ content.insertBefore(row, previousRow ? previousRow.nextSibling : content.firstChild);
19
+ }
20
+ };
21
+ const restoreFocus = (root, focused, ownedFocus) => {
22
+ // Adopted controls retain their original realm's prototype; their HTML namespace stays stable.
23
+ if (!ownedFocus || !isHtmlElement(focused) || focused === root.ownerDocument.activeElement)
24
+ return;
25
+ if (root.contains(focused))
26
+ focused.focus({ preventScroll: true });
27
+ else
28
+ root.focus({ preventScroll: true });
29
+ };
30
+ /** Data mode owns an empty VirtualList root; only its visible window and focused neighbors mount. */
31
+ export const createLumenVirtualCollectionController = (root, options) => {
32
+ if (root.childElementCount)
33
+ throw new Error('VirtualList data mode requires an empty root.');
34
+ const content = root.ownerDocument.createElement('div');
35
+ const previousMode = root.getAttribute('data-ui-virtual-list-mode');
36
+ const rows = new Map();
37
+ let destroyed = false;
38
+ let observer;
39
+ const entriesFor = (items) => {
40
+ const keys = new Set();
41
+ return Array.from(items, (item, index) => {
42
+ const key = String(options.getKey(item, index));
43
+ if (keys.has(key))
44
+ throw new Error(`VirtualList item keys must be unique: ${key}`);
45
+ keys.add(key);
46
+ return { item, key, index };
47
+ });
48
+ };
49
+ let entries = entriesFor(options.items);
50
+ content.dataset.uiVirtualListContent = '';
51
+ content.style.position = 'relative';
52
+ root.dataset.uiVirtualListMode = 'data';
53
+ root.append(content);
54
+ const updateFocus = () => {
55
+ const active = root.ownerDocument.activeElement;
56
+ const focused = [...rows].find(([, rendered]) => rendered.row.contains(active));
57
+ const entry = focused ? entries.find(item => item.key === focused[0]) : undefined;
58
+ if (entry && focused)
59
+ focused[1].row.dataset.uiVirtualListIndex = String(entry.index);
60
+ };
61
+ const getRow = (entry) => {
62
+ const index = entry.index;
63
+ let rendered = rows.get(entry.key);
64
+ if (!rendered) {
65
+ const row = root.ownerDocument.createElement('div');
66
+ row.append(options.renderItem(entry.item, index));
67
+ rendered = { index, item: entry.item, row };
68
+ rows.set(entry.key, rendered);
69
+ }
70
+ else if (rendered.item !== entry.item || rendered.index !== index) {
71
+ const previous = rendered.row.firstElementChild;
72
+ const element = options.renderItem(entry.item, index, isHtmlElement(previous) ? previous : undefined);
73
+ if (element !== previous)
74
+ rendered.row.replaceChildren(element);
75
+ rendered.item = entry.item;
76
+ rendered.index = index;
77
+ }
78
+ return rendered.row;
79
+ };
80
+ const render = (window) => {
81
+ const focused = root.ownerDocument.activeElement;
82
+ const ownedFocus = root.contains(focused);
83
+ content.style.height = `${window.totalSize}px`;
84
+ const visibleKeys = new Set();
85
+ let previousRow;
86
+ for (const index of window.indexes) {
87
+ const entry = entries[index];
88
+ if (!entry)
89
+ continue;
90
+ visibleKeys.add(entry.key);
91
+ const row = getRow(entry);
92
+ positionRow(root, row, index, entries.length, window.itemSize);
93
+ insertRow(content, row, previousRow);
94
+ previousRow = row;
95
+ }
96
+ for (const [key, rendered] of rows) {
97
+ if (!visibleKeys.has(key)) {
98
+ rendered.row.remove();
99
+ rows.delete(key);
100
+ }
101
+ }
102
+ restoreFocus(root, focused, ownedFocus);
103
+ };
104
+ const observe = () => {
105
+ observer?.destroy();
106
+ observer = observeLumenVirtualWindow(root, {
107
+ itemCount: entries.length, itemSize: options.itemSize ?? 44, overscan: options.overscan ?? 4, onChange: render
108
+ });
109
+ };
110
+ observe();
111
+ return {
112
+ update: items => {
113
+ if (destroyed)
114
+ return;
115
+ // Validate the complete new key set before replacing the live collection.
116
+ const next = entriesFor(items);
117
+ entries = next;
118
+ updateFocus();
119
+ observe();
120
+ },
121
+ destroy: () => {
122
+ destroyed = true;
123
+ observer?.destroy();
124
+ content.remove();
125
+ rows.clear();
126
+ if (previousMode === null)
127
+ delete root.dataset.uiVirtualListMode;
128
+ else
129
+ root.setAttribute('data-ui-virtual-list-mode', previousMode);
130
+ }
131
+ };
132
+ };
@@ -0,0 +1,7 @@
1
+ export interface LumenVirtualListController {
2
+ destroy: () => void;
3
+ update: () => void;
4
+ }
5
+ /** Fixed-height DOM windowing; rows remain mounted so application state is retained. */
6
+ export declare const createLumenVirtualListController: (root: HTMLElement) => LumenVirtualListController;
7
+ //# sourceMappingURL=virtual-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-list.d.ts","sourceRoot":"","sources":["../src/virtual-list.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAWD,wFAAwF;AACxF,eAAO,MAAM,gCAAgC,GAAI,MAAM,WAAW,KAAG,0BAiLpE,CAAA"}
@@ -0,0 +1,135 @@
1
+ import { getVirtualRange } from './data.js';
2
+ const getDocumentConstructors = (document) => {
3
+ const view = document.defaultView;
4
+ return {
5
+ RangeEvent: view?.CustomEvent ?? CustomEvent,
6
+ RowObserver: view?.MutationObserver ?? MutationObserver
7
+ };
8
+ };
9
+ /** Fixed-height DOM windowing; rows remain mounted so application state is retained. */
10
+ export const createLumenVirtualListController = (root) => {
11
+ if (root.getAttribute('data-ui-virtual-list-mode') === 'data' || root.getAttribute('mode') === 'data')
12
+ return { destroy: () => undefined, update: () => undefined };
13
+ const document = root.ownerDocument;
14
+ const view = document.defaultView;
15
+ const { RangeEvent, RowObserver } = getDocumentConstructors(document);
16
+ const originalRows = new Map();
17
+ const spacers = ['start', 'end'].map(position => {
18
+ const spacer = document.createElement('div');
19
+ spacer.dataset.uiVirtualListSpacer = position;
20
+ spacer.setAttribute('aria-hidden', 'true');
21
+ return spacer;
22
+ });
23
+ const [before, after] = spacers;
24
+ if (!before || !after)
25
+ throw new Error('Virtual list spacers are required');
26
+ let destroyed = false;
27
+ let previousRange = '';
28
+ const readNumber = (dataName, name, fallback, minimum) => {
29
+ const value = Number(root.getAttribute(dataName) ?? root.getAttribute(name) ?? fallback);
30
+ return Number.isFinite(value) && value >= minimum && value <= Number.MAX_SAFE_INTEGER ? value : fallback;
31
+ };
32
+ const restoreRow = (row) => {
33
+ const original = originalRows.get(row);
34
+ if (!original)
35
+ return;
36
+ row.hidden = original.hidden;
37
+ row.style.blockSize = original.blockSize;
38
+ row.style.boxSizing = original.boxSizing;
39
+ originalRows.delete(row);
40
+ };
41
+ const syncRows = () => {
42
+ const children = [...root.children].filter((child) => child.namespaceURI === 'http://www.w3.org/1999/xhtml' && child !== before && child !== after);
43
+ const childSet = new Set(children);
44
+ for (const row of originalRows.keys()) {
45
+ if (!childSet.has(row))
46
+ restoreRow(row);
47
+ }
48
+ for (const row of children) {
49
+ if (!originalRows.has(row))
50
+ originalRows.set(row, {
51
+ hidden: row.hidden,
52
+ blockSize: row.style.blockSize,
53
+ boxSizing: row.style.boxSizing
54
+ });
55
+ }
56
+ return children.filter(row => !originalRows.get(row)?.hidden);
57
+ };
58
+ const getViewport = () => {
59
+ const padding = view?.getComputedStyle(root);
60
+ const start = Number.parseFloat(padding?.paddingTop ?? '0') || 0;
61
+ const end = Number.parseFloat(padding?.paddingBottom ?? '0') || 0;
62
+ return Math.max(0, root.clientHeight - start - end);
63
+ };
64
+ const update = () => {
65
+ if (destroyed)
66
+ return;
67
+ const rows = syncRows();
68
+ const size = readNumber('data-ui-item-size', 'item-size', 44, 1);
69
+ const overscan = readNumber('data-ui-overscan', 'overscan', 4, 0);
70
+ const viewport = getViewport();
71
+ const maximumOffset = Math.max(0, rows.length * size - viewport);
72
+ if (root.scrollTop > maximumOffset)
73
+ root.scrollTop = maximumOffset;
74
+ const range = getVirtualRange(root.scrollTop, viewport, size, rows.length, overscan);
75
+ const focusedIndex = rows.findIndex(row => row.contains(document.activeElement));
76
+ // Keep the focused row and its neighbors available for native sequential Tab navigation.
77
+ if (focusedIndex >= 0) {
78
+ range.startIndex = Math.min(range.startIndex, Math.max(0, focusedIndex - 1));
79
+ range.endIndex = Math.max(range.endIndex, Math.min(rows.length - 1, focusedIndex + 1));
80
+ }
81
+ for (const [index, row] of rows.entries()) {
82
+ row.style.blockSize = `${size}px`;
83
+ row.style.boxSizing = 'border-box';
84
+ row.hidden = index < range.startIndex || index > range.endIndex;
85
+ }
86
+ before.style.blockSize = `${range.startIndex * size}px`;
87
+ after.style.blockSize = `${Math.max(0, rows.length - range.endIndex - 1) * size}px`;
88
+ // React or an application may append new rows after the trailing spacer.
89
+ if (root.firstElementChild !== before)
90
+ root.prepend(before);
91
+ if (root.lastElementChild !== after)
92
+ root.append(after);
93
+ root.dataset.uiRangeStart = String(range.startIndex);
94
+ root.dataset.uiRangeEnd = String(range.endIndex);
95
+ const rangeKey = `${range.startIndex}:${range.endIndex}:${rows.length}`;
96
+ if (rangeKey !== previousRange) {
97
+ previousRange = rangeKey;
98
+ root.dispatchEvent(new RangeEvent('ui:virtual-list-range', { bubbles: true, detail: range }));
99
+ }
100
+ };
101
+ const observer = new RowObserver(update);
102
+ const resizeObserver = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(update);
103
+ const onFocusOut = () => {
104
+ queueMicrotask(update);
105
+ };
106
+ root.addEventListener('scroll', update, { passive: true });
107
+ root.addEventListener('focusin', update);
108
+ root.addEventListener('focusout', onFocusOut);
109
+ view?.addEventListener('resize', update);
110
+ update();
111
+ observer.observe(root, {
112
+ childList: true,
113
+ attributes: true,
114
+ attributeFilter: ['data-ui-item-size', 'data-ui-overscan', 'item-size', 'overscan']
115
+ });
116
+ resizeObserver?.observe(root);
117
+ return {
118
+ update,
119
+ destroy: () => {
120
+ destroyed = true;
121
+ observer.disconnect();
122
+ resizeObserver?.disconnect();
123
+ root.removeEventListener('scroll', update);
124
+ root.removeEventListener('focusin', update);
125
+ root.removeEventListener('focusout', onFocusOut);
126
+ view?.removeEventListener('resize', update);
127
+ for (const row of originalRows.keys())
128
+ restoreRow(row);
129
+ for (const spacer of spacers)
130
+ spacer.remove();
131
+ delete root.dataset.uiRangeStart;
132
+ delete root.dataset.uiRangeEnd;
133
+ }
134
+ };
135
+ };
@@ -0,0 +1,19 @@
1
+ export interface LumenVirtualWindow {
2
+ endIndex: number;
3
+ indexes: number[];
4
+ itemSize: number;
5
+ startIndex: number;
6
+ totalSize: number;
7
+ }
8
+ export interface LumenVirtualWindowOptions {
9
+ itemCount: number;
10
+ itemSize?: number;
11
+ onChange: (window: LumenVirtualWindow) => void;
12
+ overscan?: number;
13
+ }
14
+ export declare const getLumenVirtualWindow: (offset: number, viewport: number, itemCount: number, itemSize?: number, overscan?: number, focusedIndex?: number) => LumenVirtualWindow;
15
+ export declare const observeLumenVirtualWindow: (root: HTMLElement, options: LumenVirtualWindowOptions) => {
16
+ destroy: () => void;
17
+ update: () => void;
18
+ };
19
+ //# sourceMappingURL=virtual-window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-window.d.ts","sourceRoot":"","sources":["../src/virtual-window.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AACD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAkBD,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,EACd,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,iBAAa,EACb,iBAAY,EACZ,qBAAiB,KAChB,kBAcF,CAAA;AAkBD,eAAO,MAAM,yBAAyB,GACpC,MAAM,WAAW,EAAE,SAAS,yBAAyB,KACpD;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAwE3C,CAAA"}
@@ -0,0 +1,84 @@
1
+ import { getVirtualRange } from './data.js';
2
+ const normalizeCount = (count) => Number.isSafeInteger(count) && count > 0 ? count : 0;
3
+ const normalizeSize = (size, count) => {
4
+ if (!Number.isFinite(size) || size <= 0 || size > Number.MAX_SAFE_INTEGER / Math.max(1, count))
5
+ return 44;
6
+ return size;
7
+ };
8
+ const pinFocus = (indexes, focusedIndex, count) => {
9
+ if (!Number.isSafeInteger(focusedIndex) || focusedIndex < 0 || focusedIndex >= count)
10
+ return;
11
+ const end = Math.min(count - 1, focusedIndex + 1);
12
+ for (let index = Math.max(0, focusedIndex - 1); index <= end; index += 1)
13
+ indexes.add(index);
14
+ };
15
+ export const getLumenVirtualWindow = (offset, viewport, itemCount, itemSize = 44, overscan = 4, focusedIndex = -1) => {
16
+ const count = normalizeCount(itemCount);
17
+ const size = normalizeSize(itemSize, count);
18
+ const range = getVirtualRange(offset, viewport, size, count, overscan);
19
+ const indexes = new Set();
20
+ for (let index = range.startIndex; index <= range.endIndex; index += 1)
21
+ indexes.add(index);
22
+ // Retain focused content and its Tab neighbors without mounting the intervening rows.
23
+ pinFocus(indexes, focusedIndex, count);
24
+ return {
25
+ ...range, indexes: [...indexes].sort((first, second) => first - second), itemSize: size, totalSize: count * size
26
+ };
27
+ };
28
+ const getViewport = (root) => {
29
+ const styles = root.ownerDocument.defaultView?.getComputedStyle(root);
30
+ const start = Number.parseFloat(styles?.paddingTop ?? '0') || 0;
31
+ const end = Number.parseFloat(styles?.paddingBottom ?? '0') || 0;
32
+ return Math.max(0, root.clientHeight - start - end);
33
+ };
34
+ const getFocusedIndex = (root) => {
35
+ const active = root.ownerDocument.activeElement;
36
+ // Element APIs remain available when the focused control was adopted from another document.
37
+ const row = active?.closest('[data-ui-virtual-list-index]');
38
+ return row?.closest('[data-ui-virtual-list]') === root ? Number(row.getAttribute('data-ui-virtual-list-index')) : -1;
39
+ };
40
+ export const observeLumenVirtualWindow = (root, options) => {
41
+ const view = root.ownerDocument.defaultView;
42
+ let destroyed = false;
43
+ let previous = '';
44
+ const update = () => {
45
+ if (destroyed)
46
+ return;
47
+ const viewport = getViewport(root);
48
+ const window = getLumenVirtualWindow(root.scrollTop, viewport, options.itemCount, options.itemSize, options.overscan, getFocusedIndex(root));
49
+ root.scrollTop = Math.min(root.scrollTop, Math.max(0, window.totalSize - viewport));
50
+ const signature = `${window.itemSize}:${window.totalSize}:${window.startIndex}:${window.endIndex}:${window.indexes.join(',')}`;
51
+ if (signature === previous)
52
+ return;
53
+ previous = signature;
54
+ root.dataset.uiRangeStart = String(window.startIndex);
55
+ root.dataset.uiRangeEnd = String(window.endIndex);
56
+ options.onChange(window);
57
+ const RangeEvent = view?.CustomEvent ?? CustomEvent;
58
+ root.dispatchEvent(new RangeEvent('ui:virtual-list-range', { bubbles: true, detail: { startIndex: window.startIndex, endIndex: window.endIndex } }));
59
+ };
60
+ const onFocusOut = () => {
61
+ queueMicrotask(update);
62
+ };
63
+ const ViewResizeObserver = view?.ResizeObserver ?? (typeof ResizeObserver === 'undefined' ? undefined : ResizeObserver);
64
+ const resize = ViewResizeObserver ? new ViewResizeObserver(update) : undefined;
65
+ root.addEventListener('scroll', update, { passive: true });
66
+ root.addEventListener('focusin', update);
67
+ root.addEventListener('focusout', onFocusOut);
68
+ view?.addEventListener('resize', update);
69
+ resize?.observe(root);
70
+ update();
71
+ return {
72
+ update,
73
+ destroy: () => {
74
+ destroyed = true;
75
+ resize?.disconnect();
76
+ root.removeEventListener('scroll', update);
77
+ root.removeEventListener('focusin', update);
78
+ root.removeEventListener('focusout', onFocusOut);
79
+ view?.removeEventListener('resize', update);
80
+ delete root.dataset.uiRangeStart;
81
+ delete root.dataset.uiRangeEnd;
82
+ }
83
+ };
84
+ };
@@ -0,0 +1,6 @@
1
+ export declare const lumenVisualEffectVariants: readonly ["mesh", "aurora", "spotlight", "grain", "border", "draw", "depth"];
2
+ export type LumenVisualEffectVariant = typeof lumenVisualEffectVariants[number];
3
+ export declare const normalizeLumenEffectIntensity: (value: unknown) => number;
4
+ /** Pointer tracking is optional decoration; content remains operable without this controller. */
5
+ export declare const createLumenSpotlightController: (root: HTMLElement) => (() => void);
6
+ //# sourceMappingURL=visual-effects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-effects.d.ts","sourceRoot":"","sources":["../src/visual-effects.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,yBAAyB,8EAA+E,CAAA;AACrH,MAAM,MAAM,wBAAwB,GAAG,OAAO,yBAAyB,CAAC,MAAM,CAAC,CAAA;AAE/E,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,MAAqG,CAAA;AAEpK,iGAAiG;AACjG,eAAO,MAAM,8BAA8B,GAAI,MAAM,WAAW,KAAG,CAAC,MAAM,IAAI,CA2D7E,CAAA"}
@@ -0,0 +1,44 @@
1
+ import { isLumenWorkflowMotionReduced } from './motion-workflows.js';
2
+ export const lumenVisualEffectVariants = ['mesh', 'aurora', 'spotlight', 'grain', 'border', 'draw', 'depth'];
3
+ export const normalizeLumenEffectIntensity = (value) => typeof value === 'number' && Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0.5;
4
+ /** Pointer tracking is optional decoration; content remains operable without this controller. */
5
+ export const createLumenSpotlightController = (root) => {
6
+ const view = root.ownerDocument.defaultView;
7
+ let frame;
8
+ let x = '50%';
9
+ let y = '50%';
10
+ const write = () => {
11
+ frame = undefined;
12
+ root.style.setProperty('--ui-spotlight-x', x);
13
+ root.style.setProperty('--ui-spotlight-y', y);
14
+ };
15
+ const move = (event) => {
16
+ if (event.pointerType === 'touch' || isLumenWorkflowMotionReduced(root))
17
+ return;
18
+ const rect = root.getBoundingClientRect();
19
+ x = `${Math.min(rect.width, Math.max(0, event.clientX - rect.left))}px`;
20
+ y = `${Math.min(rect.height, Math.max(0, event.clientY - rect.top))}px`;
21
+ if (frame === undefined && view)
22
+ frame = view.requestAnimationFrame(write);
23
+ };
24
+ const reset = () => {
25
+ if (frame !== undefined)
26
+ view?.cancelAnimationFrame(frame);
27
+ frame = undefined;
28
+ x = '50%';
29
+ y = '50%';
30
+ write();
31
+ };
32
+ const media = view?.matchMedia('(prefers-reduced-motion: reduce)');
33
+ media?.addEventListener('change', reset);
34
+ root.addEventListener('pointermove', move);
35
+ root.addEventListener('pointerleave', reset);
36
+ root.addEventListener('focusout', reset);
37
+ return () => {
38
+ media?.removeEventListener('change', reset);
39
+ root.removeEventListener('pointermove', move);
40
+ root.removeEventListener('pointerleave', reset);
41
+ root.removeEventListener('focusout', reset);
42
+ reset();
43
+ };
44
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Generated by `pnpm run generate:world-map-data`. Do not edit by hand.
3
+ *
4
+ * Source: maps/world-map.source.json (Natural Earth 1:50m Admin 0 Countries, public domain).
5
+ * See maps/README.md and THIRD_PARTY_NOTICES.md for provenance, projection, and precision notes.
6
+ */
7
+ import type { LumenWorldMapCountryGeometry } from './world-map.js';
8
+ export declare const lumenWorldMapCountries: readonly LumenWorldMapCountryGeometry[];
9
+ //# sourceMappingURL=world-map-data.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-map-data.generated.d.ts","sourceRoot":"","sources":["../src/world-map-data.generated.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAA;AAElE,eAAO,MAAM,sBAAsB,EAAE,SAAS,4BAA4B,EA+OzE,CAAA"}