@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.
Files changed (153) hide show
  1. package/PHONE_FLAG_LICENSE.txt +21 -0
  2. package/README.md +384 -3
  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 +58 -3
  29. package/dist/charts.d.ts.map +1 -1
  30. package/dist/charts.js +247 -18
  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/floating-panel.d.ts +24 -0
  61. package/dist/floating-panel.d.ts.map +1 -0
  62. package/dist/floating-panel.js +203 -0
  63. package/dist/forms.d.ts.map +1 -1
  64. package/dist/forms.js +18 -4
  65. package/dist/icon-data.generated.d.ts +1 -1
  66. package/dist/icon-data.generated.d.ts.map +1 -1
  67. package/dist/icon-data.generated.js +1 -1
  68. package/dist/icons.js +3 -4
  69. package/dist/illustrations.generated.d.ts +1 -1
  70. package/dist/image-comparison.d.ts +14 -0
  71. package/dist/image-comparison.d.ts.map +1 -0
  72. package/dist/image-comparison.js +48 -0
  73. package/dist/index.d.ts +37 -4
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +36 -3
  76. package/dist/loading.d.ts +34 -0
  77. package/dist/loading.d.ts.map +1 -0
  78. package/dist/loading.js +70 -0
  79. package/dist/media-viewport.d.ts +24 -0
  80. package/dist/media-viewport.d.ts.map +1 -0
  81. package/dist/media-viewport.js +119 -0
  82. package/dist/media-workspace.d.ts +27 -0
  83. package/dist/media-workspace.d.ts.map +1 -0
  84. package/dist/media-workspace.js +83 -0
  85. package/dist/message-scroller.d.ts +14 -0
  86. package/dist/message-scroller.d.ts.map +1 -0
  87. package/dist/message-scroller.js +113 -0
  88. package/dist/motion-workflows.d.ts +24 -0
  89. package/dist/motion-workflows.d.ts.map +1 -0
  90. package/dist/motion-workflows.js +220 -0
  91. package/dist/motion.d.ts +12 -0
  92. package/dist/motion.d.ts.map +1 -0
  93. package/dist/motion.js +96 -0
  94. package/dist/native-input.d.ts +25 -0
  95. package/dist/native-input.d.ts.map +1 -0
  96. package/dist/native-input.js +188 -0
  97. package/dist/phone-flags.generated.d.ts +2 -0
  98. package/dist/phone-flags.generated.d.ts.map +1 -0
  99. package/dist/phone-flags.generated.js +263 -0
  100. package/dist/phone.d.ts +4 -0
  101. package/dist/phone.d.ts.map +1 -1
  102. package/dist/phone.js +6 -1
  103. package/dist/rich-text.d.ts +7 -0
  104. package/dist/rich-text.d.ts.map +1 -1
  105. package/dist/rich-text.js +13 -0
  106. package/dist/rive.d.ts +37 -0
  107. package/dist/rive.d.ts.map +1 -0
  108. package/dist/rive.js +129 -0
  109. package/dist/schedule.d.ts.map +1 -1
  110. package/dist/schedule.js +63 -23
  111. package/dist/tab-indicator.d.ts +3 -0
  112. package/dist/tab-indicator.d.ts.map +1 -0
  113. package/dist/tab-indicator.js +57 -0
  114. package/dist/theme-audit.d.ts +16 -0
  115. package/dist/theme-audit.d.ts.map +1 -0
  116. package/dist/theme-audit.js +57 -0
  117. package/dist/theme-builder.d.ts +7 -1
  118. package/dist/theme-builder.d.ts.map +1 -1
  119. package/dist/theme-builder.js +45 -23
  120. package/dist/theme-presets.generated.d.ts +217 -0
  121. package/dist/theme-presets.generated.d.ts.map +1 -0
  122. package/dist/theme-presets.generated.js +231 -0
  123. package/dist/theme.d.ts +11 -3
  124. package/dist/theme.d.ts.map +1 -1
  125. package/dist/theme.js +56 -6
  126. package/dist/virtual-collection.d.ts +14 -0
  127. package/dist/virtual-collection.d.ts.map +1 -0
  128. package/dist/virtual-collection.js +132 -0
  129. package/dist/virtual-list.d.ts +7 -0
  130. package/dist/virtual-list.d.ts.map +1 -0
  131. package/dist/virtual-list.js +135 -0
  132. package/dist/virtual-window.d.ts +19 -0
  133. package/dist/virtual-window.d.ts.map +1 -0
  134. package/dist/virtual-window.js +84 -0
  135. package/dist/visual-effects.d.ts +6 -0
  136. package/dist/visual-effects.d.ts.map +1 -0
  137. package/dist/visual-effects.js +44 -0
  138. package/dist/world-map-data.generated.d.ts +9 -0
  139. package/dist/world-map-data.generated.d.ts.map +1 -0
  140. package/dist/world-map-data.generated.js +240 -0
  141. package/dist/world-map-navigation.d.ts +16 -0
  142. package/dist/world-map-navigation.d.ts.map +1 -0
  143. package/dist/world-map-navigation.js +53 -0
  144. package/dist/world-map-pan.d.ts +3 -0
  145. package/dist/world-map-pan.d.ts.map +1 -0
  146. package/dist/world-map-pan.js +66 -0
  147. package/dist/world-map-zoom.d.ts +13 -0
  148. package/dist/world-map-zoom.d.ts.map +1 -0
  149. package/dist/world-map-zoom.js +61 -0
  150. package/dist/world-map.d.ts +71 -0
  151. package/dist/world-map.d.ts.map +1 -0
  152. package/dist/world-map.js +143 -0
  153. package/package.json +51 -4
@@ -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
+ };
@@ -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"}
@@ -0,0 +1,188 @@
1
+ const decimalFormatter = (locale) => {
2
+ try {
3
+ return new Intl.NumberFormat(locale, { useGrouping: false });
4
+ }
5
+ catch {
6
+ return new Intl.NumberFormat('en', { useGrouping: false });
7
+ }
8
+ };
9
+ const decimalSymbols = (locale) => {
10
+ const formatter = decimalFormatter(locale);
11
+ const parts = formatter.formatToParts(-1.1);
12
+ return {
13
+ decimal: parts.find(part => part.type === 'decimal')?.value ?? '.',
14
+ digits: Array.from({ length: 10 }, (_, digit) => formatter.format(digit)),
15
+ minus: parts.find(part => part.type === 'minusSign')?.value ?? '-'
16
+ };
17
+ };
18
+ const numericDigit = (character) => {
19
+ if (!/^\p{Decimal_Number}$/u.test(character))
20
+ return null;
21
+ const code = character.codePointAt(0);
22
+ if (code === undefined)
23
+ return null;
24
+ let start = code;
25
+ while (/^\p{Decimal_Number}$/u.test(String.fromCodePoint(start - 1)))
26
+ start -= 1;
27
+ return (code - start) % 10;
28
+ };
29
+ const scanDecimalDigits = (value, separator, localizedDigits) => {
30
+ let digits = '';
31
+ let decimalSeen = false;
32
+ let scale = 0;
33
+ for (const character of value) {
34
+ const localizedDigit = localizedDigits.indexOf(character);
35
+ const digit = localizedDigit < 0 ? numericDigit(character) : localizedDigit;
36
+ if (digit !== null) {
37
+ digits += String(digit);
38
+ if (decimalSeen)
39
+ scale += 1;
40
+ }
41
+ else if (character === separator && !decimalSeen)
42
+ decimalSeen = true;
43
+ else
44
+ return null;
45
+ }
46
+ return { digits, scale };
47
+ };
48
+ export const parseLumenDecimalDraft = (value, locale) => {
49
+ if (value === '')
50
+ return { kind: 'empty' };
51
+ if (value.length > 128)
52
+ return { kind: 'invalid' };
53
+ const symbols = decimalSymbols(locale);
54
+ const negative = value.startsWith(symbols.minus) || value.startsWith('-');
55
+ const signLength = value.startsWith(symbols.minus) ? symbols.minus.length : 1;
56
+ const unsigned = negative ? value.slice(signLength) : value;
57
+ const scanned = scanDecimalDigits(unsigned, symbols.decimal, symbols.digits);
58
+ if (!scanned)
59
+ return { kind: 'invalid' };
60
+ if (scanned.digits === '' || unsigned.endsWith(symbols.decimal))
61
+ return { kind: 'incomplete' };
62
+ return { coefficient: BigInt(scanned.digits) * (negative ? -1n : 1n), kind: 'valid', scale: scanned.scale };
63
+ };
64
+ const parseConfiguration = (value) => {
65
+ if (value === undefined)
66
+ return undefined;
67
+ for (const character of value) {
68
+ if (!'0123456789.-'.includes(character))
69
+ throw new RangeError('Decimal configuration must use ASCII digits.');
70
+ }
71
+ const parsed = parseLumenDecimalDraft(value, 'en-US');
72
+ if (parsed.kind !== 'valid')
73
+ throw new RangeError('Decimal configuration must be a complete ungrouped decimal of at most 128 characters.');
74
+ return parsed;
75
+ };
76
+ const compareDecimals = (left, right) => {
77
+ const scale = Math.max(left.scale, right.scale);
78
+ const difference = left.coefficient * 10n ** BigInt(scale - left.scale) -
79
+ right.coefficient * 10n ** BigInt(scale - right.scale);
80
+ return difference < 0n ? -1 : Number(difference > 0n);
81
+ };
82
+ const decimalConfiguration = (options) => {
83
+ const min = parseConfiguration(options.min);
84
+ const max = parseConfiguration(options.max);
85
+ const step = parseConfiguration(options.step ?? '1');
86
+ if (!step || step.coefficient <= 0n)
87
+ throw new RangeError('Decimal step must be positive.');
88
+ if (min && max && compareDecimals(min, max) > 0)
89
+ throw new RangeError('Decimal minimum must not exceed maximum.');
90
+ return { max, min, step };
91
+ };
92
+ export const isLumenDecimalInBounds = (value, options = {}) => {
93
+ const { max, min } = decimalConfiguration(options);
94
+ const draft = parseLumenDecimalDraft(value, options.locale);
95
+ return draft.kind === 'valid' &&
96
+ (!min || compareDecimals(draft, min) >= 0) &&
97
+ (!max || compareDecimals(draft, max) <= 0);
98
+ };
99
+ const localizeDigits = (value, digits) => {
100
+ let result = '';
101
+ for (const digit of value)
102
+ result += digits[Number(digit)] ?? digit;
103
+ return result;
104
+ };
105
+ const startingDecimal = (draft, min, max) => {
106
+ if (draft.kind === 'valid')
107
+ return draft;
108
+ if (draft.kind !== 'empty')
109
+ return null;
110
+ if (min)
111
+ return min;
112
+ if (max && max.coefficient < 0n)
113
+ return max;
114
+ return { coefficient: 0n, kind: 'valid', scale: 0 };
115
+ };
116
+ const formatDecimal = (value, locale) => {
117
+ const symbols = decimalSymbols(locale);
118
+ const negative = value.coefficient < 0n;
119
+ const absolute = negative ? -value.coefficient : value.coefficient;
120
+ const digits = absolute.toString().padStart(value.scale + 1, '0');
121
+ let fraction = value.scale > 0 ? digits.slice(-value.scale) : '';
122
+ while (fraction.endsWith('0'))
123
+ fraction = fraction.slice(0, -1);
124
+ const integer = value.scale > 0 ? digits.slice(0, -value.scale) : digits;
125
+ const localized = localizeDigits(integer, symbols.digits);
126
+ const localizedFraction = localizeDigits(fraction, symbols.digits);
127
+ return `${negative ? symbols.minus : ''}${localized}${fraction ? symbols.decimal + localizedFraction : ''}`;
128
+ };
129
+ export const stepLumenDecimalDraft = (value, direction, options = {}) => {
130
+ const { max, min, step } = decimalConfiguration(options);
131
+ const draft = parseLumenDecimalDraft(value, options.locale);
132
+ const start = startingDecimal(draft, min, max);
133
+ if (!start)
134
+ return null;
135
+ const scale = Math.max(start.scale, step.scale);
136
+ let result = {
137
+ coefficient: start.coefficient * 10n ** BigInt(scale - start.scale) +
138
+ BigInt(direction) * step.coefficient * 10n ** BigInt(scale - step.scale),
139
+ kind: 'valid',
140
+ scale
141
+ };
142
+ if (min && compareDecimals(result, min) < 0)
143
+ result = min;
144
+ if (max && compareDecimals(result, max) > 0)
145
+ result = max;
146
+ const formatted = formatDecimal(result, options.locale);
147
+ return formatted.length <= 128 ? formatted : null;
148
+ };
149
+ /** Single-editor numeric OTP normalization; separators are accepted, unrelated text is rejected. */
150
+ const validateOTPLength = (length) => {
151
+ if (!Number.isInteger(length) || length < 1 || length > 12) {
152
+ throw new RangeError('OTP length must be between 1 and 12.');
153
+ }
154
+ };
155
+ export const normalizeLumenNumericOTP = (proposal, length = 6) => {
156
+ validateOTPLength(length);
157
+ if (proposal.length > 128)
158
+ return null;
159
+ let result = '';
160
+ for (const character of proposal) {
161
+ const digit = numericDigit(character);
162
+ if (digit !== null)
163
+ result += String(digit);
164
+ else if (character !== '-' && character.trim() !== '')
165
+ return null;
166
+ if (result.length > length)
167
+ return null;
168
+ }
169
+ return result;
170
+ };
171
+ const isTimePart = (value, limit) => (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value < limit);
172
+ export const isLumenTimeSelection = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value) &&
173
+ 'hour' in value && 'minute' in value && isTimePart(value.hour, 24) && isTimePart(value.minute, 60));
174
+ const timeMinutes = (time) => time.hour * 60 + time.minute;
175
+ const validateTimeBounds = (min, max) => {
176
+ if ((min && !isLumenTimeSelection(min)) || (max && !isLumenTimeSelection(max))) {
177
+ throw new RangeError('Time bounds must contain valid wall-clock values.');
178
+ }
179
+ if (min && max && timeMinutes(min) > timeMinutes(max)) {
180
+ throw new RangeError('Time bounds must be ordered within one day.');
181
+ }
182
+ };
183
+ export const isLumenTimeInBounds = (value, min, max) => {
184
+ validateTimeBounds(min, max);
185
+ return isLumenTimeSelection(value) &&
186
+ (!min || timeMinutes(value) >= timeMinutes(min)) &&
187
+ (!max || timeMinutes(value) <= timeMinutes(max));
188
+ };
@@ -0,0 +1,2 @@
1
+ export declare const lumenPhoneFlagSources: Readonly<Record<string, string>>;
2
+ //# sourceMappingURL=phone-flags.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phone-flags.generated.d.ts","sourceRoot":"","sources":["../src/phone-flags.generated.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoQlE,CAAA"}