@santi020k/lumen-core 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/PHONE_FLAG_LICENSE.txt +21 -0
  2. package/README.md +363 -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/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
@@ -0,0 +1,22 @@
1
+ export interface LumenAmountOptions {
2
+ locale?: string;
3
+ fractionDigits?: number;
4
+ allowNegative?: boolean;
5
+ }
6
+ export interface LumenAmountChangeDetail {
7
+ /** Editable ASCII decimal draft; intermediate '-' and trailing decimal points are retained. */
8
+ draft: string;
9
+ /** Complete decimal string. Undefined for an empty or intermediate draft. */
10
+ value: string | undefined;
11
+ }
12
+ export declare const getLumenAmountValue: (draft: string) => string | undefined;
13
+ export declare const formatLumenAmountDraft: (draft: string, options?: LumenAmountOptions) => string;
14
+ export declare const parseLumenAmountDraft: (source: string, options?: LumenAmountOptions, strictGrouping?: boolean) => string | undefined;
15
+ export interface LumenAmountFieldController {
16
+ destroy: () => void;
17
+ /** Rebind reset handling after moving the existing control to another document or shadow root. */
18
+ refresh: () => void;
19
+ setValue: (draft: string) => void;
20
+ }
21
+ export declare const createLumenAmountFieldController: (root: HTMLElement, onChange?: (detail: LumenAmountChangeDetail) => void) => LumenAmountFieldController;
22
+ //# sourceMappingURL=amount-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amount-field.d.ts","sourceRoot":"","sources":["../src/amount-field.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,+FAA+F;IAC/F,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B;AAqDD,eAAO,MAAM,mBAAmB,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,SAI5D,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,OAAO,MAAM,EAAE,UAAS,kBAAuB,KAAG,MAcxF,CAAA;AAgDD,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,EAAE,UAAS,kBAAuB,EAAE,wBAAqB,KACtE,MAAM,GAAG,SAYX,CAAA;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,kGAAkG;IAClG,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAeD,eAAO,MAAM,gCAAgC,GAC3C,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,KACtE,0BAsIF,CAAA"}
@@ -0,0 +1,207 @@
1
+ const amountLimit = 1024;
2
+ const validateFractionDigits = (digits) => {
3
+ if (!Number.isInteger(digits) || digits < 0 || digits > 20) {
4
+ throw new RangeError('Amount fractionDigits must be an integer from 0 to 20.');
5
+ }
6
+ };
7
+ const amountFormat = (options) => {
8
+ const fractionDigits = options.fractionDigits ?? 2;
9
+ let locale = options.locale ?? 'en-US';
10
+ validateFractionDigits(fractionDigits);
11
+ let formatter;
12
+ try {
13
+ formatter = new Intl.NumberFormat(locale, { useGrouping: true, maximumFractionDigits: 0 });
14
+ }
15
+ catch {
16
+ locale = 'en-US';
17
+ formatter = new Intl.NumberFormat(locale, { useGrouping: true, maximumFractionDigits: 0 });
18
+ }
19
+ const decimal = new Intl.NumberFormat(locale).formatToParts(1.1).find(part => part.type === 'decimal')?.value ?? '.';
20
+ const group = formatter.formatToParts(1000000).find(part => part.type === 'group')?.value;
21
+ const minus = formatter.formatToParts(-1).find(part => part.type === 'minusSign')?.value ?? '-';
22
+ const digitFormatter = new Intl.NumberFormat(locale, { useGrouping: false });
23
+ const digits = Array.from({ length: 10 }, (_, digit) => digitFormatter.format(digit));
24
+ return { formatter, decimal, group, minus, digits, fractionDigits };
25
+ };
26
+ const validDraft = (draft, options) => {
27
+ if (draft.length > amountLimit)
28
+ return false;
29
+ const { fractionDigits } = amountFormat(options);
30
+ const negative = draft.startsWith('-');
31
+ if (negative && !options.allowNegative)
32
+ return false;
33
+ const unsigned = negative ? draft.slice(1) : draft;
34
+ const pieces = unsigned.split('.');
35
+ if (pieces.length > 2 || (pieces.length === 2 && fractionDigits === 0))
36
+ return false;
37
+ const fraction = pieces[1] ?? '';
38
+ return pieces.every(piece => /^[0-9]*$/u.test(piece)) && fraction.length <= fractionDigits;
39
+ };
40
+ export const getLumenAmountValue = (draft) => {
41
+ if (!/^-?[0-9]+(?:\.[0-9]+)?$/u.test(draft) || draft.length > amountLimit)
42
+ return undefined;
43
+ return draft;
44
+ };
45
+ export const formatLumenAmountDraft = (draft, options = {}) => {
46
+ if (!validDraft(draft, options))
47
+ throw new RangeError('Amount value must be an ASCII decimal draft within the configured precision.');
48
+ if (draft === '')
49
+ return '';
50
+ const { formatter, decimal, minus, digits } = amountFormat(options);
51
+ const negative = draft.startsWith('-');
52
+ const unsigned = negative ? draft.slice(1) : draft;
53
+ const [integer = '', fraction] = unsigned.split('.');
54
+ const formattedInteger = integer ? formatter.format(BigInt(integer)) : '';
55
+ const localizedFraction = Array.from(fraction ?? '', digit => digits[Number(digit)] ?? digit).join('');
56
+ // Use an unsigned formatter so intermediate negative drafts never acquire a rounded sign.
57
+ return `${negative ? minus : ''}${formattedInteger}${fraction !== undefined ? decimal + localizedFraction : ''}`;
58
+ };
59
+ /** Strict grouping for paste; ordinary editing can temporarily disturb existing group separators. */
60
+ const normalizeLeadingDecimal = (draft) => {
61
+ if (draft.startsWith('.'))
62
+ return `0${draft}`;
63
+ return draft.startsWith('-.') ? `-0${draft.slice(1)}` : draft;
64
+ };
65
+ const translateAmountCharacters = (source, options) => {
66
+ const { decimal, group, minus, digits } = amountFormat(options);
67
+ const characters = new Map(digits.map((digit, index) => [digit, String(index)]));
68
+ for (let index = 0; index < 10; index += 1)
69
+ characters.set(String(index), String(index));
70
+ characters.set(decimal, '.');
71
+ characters.set(minus, '-');
72
+ characters.set('-', '-');
73
+ if (group)
74
+ characters.set(group, '');
75
+ for (const mark of ['\u061c', '\u200e', '\u200f'])
76
+ characters.set(mark, '');
77
+ let draft = '';
78
+ for (const character of source.trim()) {
79
+ const translated = characters.get(character);
80
+ if (translated === undefined)
81
+ return undefined;
82
+ draft += translated;
83
+ }
84
+ return draft;
85
+ };
86
+ const validGrouping = (source, draft, options) => {
87
+ const { decimal, group } = amountFormat(options);
88
+ if (!group || !source.includes(group))
89
+ return true;
90
+ const [integer, fraction = ''] = source.trim().split(decimal);
91
+ return !fraction.includes(group) && integer === formatLumenAmountDraft(draft, options).split(decimal)[0];
92
+ };
93
+ export const parseLumenAmountDraft = (source, options = {}, strictGrouping = true) => {
94
+ if (source.length > amountLimit)
95
+ return undefined;
96
+ const translated = translateAmountCharacters(source, options);
97
+ if (translated === undefined || !validDraft(translated, options))
98
+ return undefined;
99
+ const draft = normalizeLeadingDecimal(translated);
100
+ if (strictGrouping && !validGrouping(source, draft, options))
101
+ return undefined;
102
+ return draft;
103
+ };
104
+ const placeAmountCaret = (input, group, logical) => {
105
+ let caret = 0;
106
+ let count = 0;
107
+ while (caret < input.value.length && count < logical) {
108
+ if (input.value[caret] !== group)
109
+ count += 1;
110
+ caret += 1;
111
+ }
112
+ input.setSelectionRange(caret, caret);
113
+ };
114
+ export const createLumenAmountFieldController = (root, onChange) => {
115
+ const input = root.querySelector('[data-ui-amount-input]');
116
+ const submission = root.querySelector('[data-ui-amount-value]');
117
+ if (!input || !submission)
118
+ throw new Error('AmountField requires visible and submission inputs.');
119
+ const options = {
120
+ locale: root.getAttribute('locale') ?? 'en-US',
121
+ fractionDigits: Number(root.getAttribute('fraction-digits') ?? 2),
122
+ allowNegative: root.hasAttribute('allow-negative')
123
+ };
124
+ const initial = root.getAttribute('default-value') ?? root.getAttribute('value') ?? '';
125
+ let draft = root.getAttribute('value') ?? initial;
126
+ let composing = false;
127
+ let destroyed = false;
128
+ let resetTimer;
129
+ const sync = () => {
130
+ input.value = formatLumenAmountDraft(draft, options);
131
+ submission.value = getLumenAmountValue(draft) ?? '';
132
+ input.setCustomValidity(draft !== '' && getLumenAmountValue(draft) === undefined ? root.getAttribute('invalid-message') ?? 'Enter a complete amount.' : '');
133
+ };
134
+ const group = amountFormat(options).group;
135
+ const logicalCount = (text) => Array.from(text).filter(character => character !== group).length;
136
+ const emitChange = () => {
137
+ const detail = { draft, value: getLumenAmountValue(draft) };
138
+ const EventType = root.ownerDocument.defaultView?.CustomEvent;
139
+ if (EventType)
140
+ root.dispatchEvent(new EventType('ui:amount-change', { bubbles: true, detail }));
141
+ onChange?.(detail);
142
+ };
143
+ const update = (event) => {
144
+ if (composing || input.matches(':disabled') || input.readOnly)
145
+ return;
146
+ const logical = logicalCount(input.value.slice(0, input.selectionStart ?? input.value.length));
147
+ const parsed = parseLumenAmountDraft(input.value, options, 'inputType' in event && event.inputType === 'insertFromPaste');
148
+ if (parsed === undefined) {
149
+ sync();
150
+ input.setCustomValidity(root.getAttribute('invalid-message') ?? 'Enter a valid amount using the displayed locale and precision.');
151
+ return;
152
+ }
153
+ draft = parsed;
154
+ sync();
155
+ placeAmountCaret(input, group, logical);
156
+ emitChange();
157
+ };
158
+ const startComposition = () => {
159
+ composing = true;
160
+ };
161
+ const endComposition = (event) => {
162
+ composing = false;
163
+ update(event);
164
+ };
165
+ const reset = (event) => {
166
+ if (event.target !== input.form)
167
+ return;
168
+ clearTimeout(resetTimer);
169
+ resetTimer = setTimeout(() => {
170
+ if (destroyed || event.defaultPrevented || event.target !== input.form)
171
+ return;
172
+ draft = root.getAttribute('default-value') ?? initial;
173
+ sync();
174
+ onChange?.({ draft, value: getLumenAmountValue(draft) });
175
+ });
176
+ };
177
+ sync();
178
+ input.addEventListener('input', update);
179
+ input.addEventListener('compositionstart', startComposition);
180
+ input.addEventListener('compositionend', endComposition);
181
+ let eventRoot;
182
+ const refresh = () => {
183
+ const current = input.getRootNode();
184
+ if (destroyed || current === eventRoot)
185
+ return;
186
+ clearTimeout(resetTimer);
187
+ eventRoot?.removeEventListener('reset', reset, { capture: true });
188
+ eventRoot = current;
189
+ eventRoot.addEventListener('reset', reset, { capture: true });
190
+ };
191
+ refresh();
192
+ return {
193
+ refresh,
194
+ setValue: value => {
195
+ draft = value;
196
+ sync();
197
+ },
198
+ destroy: () => {
199
+ destroyed = true;
200
+ clearTimeout(resetTimer);
201
+ input.removeEventListener('input', update);
202
+ input.removeEventListener('compositionstart', startComposition);
203
+ input.removeEventListener('compositionend', endComposition);
204
+ eventRoot?.removeEventListener('reset', reset, { capture: true });
205
+ }
206
+ };
207
+ };
@@ -0,0 +1,15 @@
1
+ export type LumenAttachmentPreviewState = 'error' | 'loading' | 'ready' | 'unavailable';
2
+ export interface LumenAttachmentPreviewLabels {
3
+ error: string;
4
+ loading: string;
5
+ unavailable: string;
6
+ }
7
+ export declare const lumenAttachmentPreviewLabels: Readonly<LumenAttachmentPreviewLabels>;
8
+ export declare const resolveLumenAttachmentPreviewState: (src?: unknown, contentType?: unknown, state?: LumenAttachmentPreviewState, failed?: boolean) => LumenAttachmentPreviewState;
9
+ export interface LumenAttachmentPreviewController {
10
+ destroy: () => void;
11
+ refresh: () => void;
12
+ }
13
+ /** Enhances a browser-owned image; never fetches a file or embeds a document. */
14
+ export declare const createLumenAttachmentPreviewController: (root: HTMLElement) => LumenAttachmentPreviewController;
15
+ //# sourceMappingURL=attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,2BAA2B,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,CAAA;AAEvF,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,4BAA4B,EAAE,QAAQ,CAAC,4BAA4B,CAI/E,CAAA;AAQD,eAAO,MAAM,kCAAkC,GAC7C,MAAM,OAAO,EACb,cAAc,OAAO,EACrB,QAAQ,2BAA2B,EACnC,gBAAc,KACb,2BAMF,CAAA;AAID,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,GAAI,MAAM,WAAW,KAAG,gCAqI1E,CAAA"}
@@ -0,0 +1,123 @@
1
+ export const lumenAttachmentPreviewLabels = {
2
+ error: 'Could not load the preview.',
3
+ loading: 'Loading preview…',
4
+ unavailable: 'A preview is not available for this file.'
5
+ };
6
+ const isImageContentType = (contentType) => {
7
+ if (contentType === undefined || contentType === null || contentType === '')
8
+ return true;
9
+ return typeof contentType === 'string' && contentType.trim().toLowerCase().startsWith('image/');
10
+ };
11
+ export const resolveLumenAttachmentPreviewState = (src, contentType, state, failed = false) => {
12
+ if (state === 'loading' || state === 'error' || state === 'unavailable')
13
+ return state;
14
+ if (typeof src !== 'string' || !src.trim() || !isImageContentType(contentType))
15
+ return 'unavailable';
16
+ return failed ? 'error' : 'ready';
17
+ };
18
+ const requestedState = (value) => value === 'error' || value === 'loading' || value === 'ready' || value === 'unavailable' ? value : undefined;
19
+ /** Enhances a browser-owned image; never fetches a file or embeds a document. */
20
+ export const createLumenAttachmentPreviewController = (root) => {
21
+ const owned = (selector) => Array.from(root.querySelectorAll(selector)).find(element => element.closest('[data-ui-attachment-preview]') === root);
22
+ const currentImage = () => {
23
+ const candidate = owned('[data-ui-attachment-preview-image]');
24
+ const isImage = (element) => element?.namespaceURI === 'http://www.w3.org/1999/xhtml' && element.localName === 'img';
25
+ return isImage(candidate) ? candidate : undefined;
26
+ };
27
+ let image = currentImage();
28
+ const abort = new AbortController();
29
+ const cachedFailure = (candidate) => Boolean(candidate?.getAttribute('src') && candidate.complete && candidate.naturalWidth === 0);
30
+ const attribute = (name) => root.getAttribute(name) ?? root.getAttribute(`data-ui-attachment-preview-${name}`);
31
+ let failed = cachedFailure(image);
32
+ let source = image?.getAttribute('src') ?? '';
33
+ let retryKey = attribute('retry-key') ?? '';
34
+ let previous;
35
+ let destroyed = false;
36
+ const reloadImage = () => {
37
+ if (image && source)
38
+ image.setAttribute('src', source);
39
+ };
40
+ const updateSource = () => {
41
+ const nextImage = currentImage();
42
+ if (image !== nextImage) {
43
+ image = nextImage;
44
+ failed = cachedFailure(image);
45
+ }
46
+ const nextSource = image?.getAttribute('src') ?? '';
47
+ const nextRetryKey = attribute('retry-key') ?? '';
48
+ if (source === nextSource && retryKey === nextRetryKey)
49
+ return;
50
+ const retry = retryKey !== nextRetryKey;
51
+ source = nextSource;
52
+ retryKey = nextRetryKey;
53
+ failed = !retry && cachedFailure(image);
54
+ if (retry)
55
+ reloadImage();
56
+ };
57
+ const renderState = (state) => {
58
+ const media = owned('[data-slot="attachment-preview-media"]');
59
+ const message = owned('[data-ui-attachment-preview-message]');
60
+ root.dataset.state = state;
61
+ root.setAttribute('aria-busy', String(state === 'loading'));
62
+ if (media)
63
+ media.hidden = state !== 'ready';
64
+ if (!message)
65
+ return;
66
+ message.hidden = state === 'ready';
67
+ if (state !== 'ready')
68
+ message.textContent = attribute(`${state}-label`) ?? lumenAttachmentPreviewLabels[state];
69
+ };
70
+ const refresh = () => {
71
+ if (destroyed)
72
+ return;
73
+ updateSource();
74
+ const state = resolveLumenAttachmentPreviewState(source, attribute('content-type'), requestedState(attribute('state')), failed);
75
+ renderState(state);
76
+ if (previous !== undefined && previous !== state) {
77
+ const EventConstructor = root.ownerDocument.defaultView?.CustomEvent ?? CustomEvent;
78
+ root.dispatchEvent(new EventConstructor('ui:attachment-preview-change', { bubbles: true, detail: { state } }));
79
+ }
80
+ previous = state;
81
+ };
82
+ root.addEventListener('error', event => {
83
+ if (!root.isConnected || event.target !== currentImage())
84
+ return;
85
+ failed = true;
86
+ refresh();
87
+ }, { capture: true, signal: abort.signal });
88
+ root.addEventListener('load', event => {
89
+ if (!root.isConnected || event.target !== currentImage())
90
+ return;
91
+ failed = false;
92
+ refresh();
93
+ }, { capture: true, signal: abort.signal });
94
+ const observer = new MutationObserver(records => {
95
+ if (records.some(record => record.type === 'attributes' || Array.from(record.addedNodes).some(node => node.nodeType === 1) || Array.from(record.removedNodes).some(node => node.nodeType === 1)))
96
+ refresh();
97
+ });
98
+ observer.observe(root, { subtree: true,
99
+ childList: true,
100
+ attributes: true,
101
+ attributeFilter: [
102
+ 'src',
103
+ 'state',
104
+ 'content-type',
105
+ 'retry-key',
106
+ 'error-label',
107
+ 'loading-label',
108
+ 'unavailable-label',
109
+ 'data-ui-attachment-preview-state',
110
+ 'data-ui-attachment-preview-content-type',
111
+ 'data-ui-attachment-preview-retry-key',
112
+ 'data-ui-attachment-preview-error-label',
113
+ 'data-ui-attachment-preview-loading-label',
114
+ 'data-ui-attachment-preview-unavailable-label'
115
+ ] });
116
+ refresh();
117
+ return { refresh,
118
+ destroy: () => {
119
+ destroyed = true;
120
+ abort.abort();
121
+ observer.disconnect();
122
+ } };
123
+ };
@@ -0,0 +1,33 @@
1
+ import { type LumenChartDomain, type LumenChartTone } from './charts.js';
2
+ export interface LumenBulletRange {
3
+ end: number;
4
+ label: string;
5
+ tone?: LumenChartTone;
6
+ }
7
+ export interface LumenBulletOptions {
8
+ domain?: LumenChartDomain | undefined;
9
+ ranges?: readonly LumenBulletRange[] | undefined;
10
+ }
11
+ /** A zero-based actual-versus-target comparison with optional qualitative ranges. */
12
+ export declare const createLumenBulletGeometry: (value: number | null, target: number, options?: LumenBulletOptions) => {
13
+ domain: LumenChartDomain;
14
+ ranges: {
15
+ start: number;
16
+ startRatio: number;
17
+ endRatio: number;
18
+ end: number;
19
+ label: string;
20
+ tone?: LumenChartTone;
21
+ }[];
22
+ target: number;
23
+ targetRatio: number;
24
+ ticks: {
25
+ value: number;
26
+ position: number;
27
+ }[];
28
+ valid: boolean;
29
+ value: number | null;
30
+ valueStartRatio: number;
31
+ valueWidthRatio: number;
32
+ };
33
+ //# sourceMappingURL=bullet-chart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bullet-chart.d.ts","sourceRoot":"","sources":["../src/bullet-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAyC,MAAM,aAAa,CAAA;AAE/G,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,cAAc,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAA;IACrC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,CAAA;CACjD;AAoED,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,GAAI,OAAO,MAAM,GAAG,IAAI,EAAE,QAAQ,MAAM,EAAE,UAAS,kBAAuB;;;;;;aA7EzG,MAAM;eACJ,MAAM;eACN,cAAc;;;;;;;;;;;;CAyGtB,CAAA"}
@@ -0,0 +1,80 @@
1
+ import { lumenChartTones, scaleLumenChartValue } from './charts.js';
2
+ const validBulletTone = (value) => value === undefined || lumenChartTones.some(tone => tone === value);
3
+ const validBulletRange = (range) => {
4
+ if (typeof range !== 'object' || range === null)
5
+ return false;
6
+ if (!('end' in range) || !('label' in range))
7
+ return false;
8
+ return Number.isFinite(range.end) && typeof range.label === 'string' &&
9
+ validBulletTone('tone' in range ? range.tone : undefined);
10
+ };
11
+ const validatedBulletRanges = (input) => {
12
+ if (!Array.isArray(input))
13
+ return null;
14
+ const rows = Array.from(input);
15
+ return rows.every(validBulletRange) ? rows : null;
16
+ };
17
+ const automaticBulletDomain = (values) => {
18
+ let minimum = 0;
19
+ let maximum = 0;
20
+ for (const number of values) {
21
+ if (!Number.isFinite(number))
22
+ continue;
23
+ minimum = Math.min(minimum, number);
24
+ maximum = Math.max(maximum, number);
25
+ }
26
+ return { min: minimum, max: minimum === maximum ? minimum + 1 : maximum };
27
+ };
28
+ const validBulletDomain = (domain) => {
29
+ const finite = Number.isFinite(domain.min) && Number.isFinite(domain.max);
30
+ return finite && domain.min < domain.max && domain.min <= 0 && domain.max >= 0;
31
+ };
32
+ const orderedBulletRanges = (ranges, domain) => {
33
+ let previousEnd = domain.min;
34
+ return ranges.every(range => {
35
+ const valid = range.label.trim().length > 0 && range.end > previousEnd;
36
+ previousEnd = range.end;
37
+ return valid;
38
+ });
39
+ };
40
+ const resolveBulletModel = (value, target, options) => {
41
+ const { ranges: configuredRanges = [] } = options;
42
+ const inputRanges = validatedBulletRanges(configuredRanges);
43
+ const finiteRanges = inputRanges !== null;
44
+ const ranges = [...inputRanges ?? []].sort((left, right) => left.end - right.end);
45
+ const values = [0, target, ...ranges.map(range => range.end), ...value === null ? [] : [value]];
46
+ const requested = options.domain ?? automaticBulletDomain(values);
47
+ const domain = validBulletDomain(requested) ? requested : { min: 0, max: 1 };
48
+ const withinDomain = values.every(number => Number.isFinite(number) && number >= domain.min && number <= domain.max);
49
+ const valid = finiteRanges && validBulletDomain(requested) && withinDomain && orderedBulletRanges(ranges, domain);
50
+ return { domain, ranges, valid };
51
+ };
52
+ /** A zero-based actual-versus-target comparison with optional qualitative ranges. */
53
+ export const createLumenBulletGeometry = (value, target, options = {}) => {
54
+ const { domain, ranges, valid } = resolveBulletModel(value, target, options);
55
+ const position = (number) => scaleLumenChartValue(number, domain, 0, 1);
56
+ const zero = position(0);
57
+ const actual = valid && value !== null ? position(value) : zero;
58
+ let start = domain.min;
59
+ return {
60
+ domain,
61
+ ranges: valid ?
62
+ ranges.map(range => {
63
+ const segment = { ...range, start, startRatio: position(start), endRatio: position(range.end) };
64
+ start = range.end;
65
+ return segment;
66
+ }) :
67
+ [],
68
+ target,
69
+ targetRatio: valid ? position(target) : zero,
70
+ ticks: valid ?
71
+ [domain.min, domain.min / 2 + domain.max / 2, domain.max].map(tick => ({
72
+ value: tick, position: position(tick)
73
+ })) :
74
+ [],
75
+ valid,
76
+ value,
77
+ valueStartRatio: Math.min(zero, actual),
78
+ valueWidthRatio: Math.abs(actual - zero)
79
+ };
80
+ };
@@ -0,0 +1,32 @@
1
+ import type { LumenChartDatum, LumenHeatmapDatum, LumenRangeDatum } from './charts.js';
2
+ /** Data identities and values, never pixel coordinates or application navigation. */
3
+ export type LumenChartDatumActivationDetail = {
4
+ datumId?: string;
5
+ kind: 'series';
6
+ seriesId: string;
7
+ x: number | string;
8
+ y: number;
9
+ } | {
10
+ datumId?: string;
11
+ kind: 'heatmap';
12
+ value: number;
13
+ x: number | string;
14
+ y: number | string;
15
+ } | {
16
+ datumId?: string;
17
+ high: number;
18
+ kind: 'range';
19
+ low: number;
20
+ x: number | string;
21
+ };
22
+ /** Validate untrusted event data and copy only the documented fields. */
23
+ export declare const parseLumenChartDatumActivation: (value: unknown) => LumenChartDatumActivationDetail | null;
24
+ export declare const createLumenChartDatumActivation: (seriesId: string, datum: LumenChartDatum) => LumenChartDatumActivationDetail | null;
25
+ export declare const createLumenHeatmapDatumActivation: (datum: LumenHeatmapDatum) => LumenChartDatumActivationDetail | null;
26
+ export declare const createLumenRangeDatumActivation: (datum: LumenRangeDatum) => LumenChartDatumActivationDetail | null;
27
+ export interface LumenChartActivationController {
28
+ destroy: () => void;
29
+ }
30
+ /** Native buttons provide the keyboard path; decorative SVG marks use the same click path. */
31
+ export declare const createLumenChartActivationController: (root: HTMLElement) => LumenChartActivationController;
32
+ //# sourceMappingURL=chart-activation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-activation.d.ts","sourceRoot":"","sources":["../src/chart-activation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEtF,qFAAqF;AACrF,MAAM,MAAM,+BAA+B,GACvC;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GACvF;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAC5F;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AAqCpF,yEAAyE;AACzE,eAAO,MAAM,8BAA8B,GAAI,OAAO,OAAO,KAAG,+BAA+B,GAAG,IAUjG,CAAA;AAED,eAAO,MAAM,+BAA+B,GAC1C,UAAU,MAAM,EAChB,OAAO,eAAe,KACrB,+BAA+B,GAAG,IAMnC,CAAA;AAEF,eAAO,MAAM,iCAAiC,GAC5C,OAAO,iBAAiB,KACvB,+BAA+B,GAAG,IAMnC,CAAA;AAEF,eAAO,MAAM,+BAA+B,GAC1C,OAAO,eAAe,KACrB,+BAA+B,GAAG,IAMnC,CAAA;AAEF,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AA0BD,8FAA8F;AAC9F,eAAO,MAAM,oCAAoC,GAAI,MAAM,WAAW,KAAG,8BAqBxE,CAAA"}
@@ -0,0 +1,97 @@
1
+ const isAxisValue = (value) => typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));
2
+ const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
3
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
4
+ const parseSeries = (value) => {
5
+ if (typeof value.seriesId !== 'string' || !isAxisValue(value.x) || !isFiniteNumber(value.y))
6
+ return null;
7
+ return { kind: 'series', seriesId: value.seriesId, x: value.x, y: value.y };
8
+ };
9
+ const parseHeatmap = (value) => {
10
+ if (!isAxisValue(value.x) || !isAxisValue(value.y) || !isFiniteNumber(value.value))
11
+ return null;
12
+ return { kind: 'heatmap', value: value.value, x: value.x, y: value.y };
13
+ };
14
+ const parseRange = (value) => {
15
+ if (!isAxisValue(value.x) || !isFiniteNumber(value.low) || !isFiniteNumber(value.high) ||
16
+ value.low > value.high)
17
+ return null;
18
+ return { high: value.high, kind: 'range', low: value.low, x: value.x };
19
+ };
20
+ const parseDatum = (value) => {
21
+ switch (value.kind) {
22
+ case 'series': return parseSeries(value);
23
+ case 'heatmap': return parseHeatmap(value);
24
+ case 'range': return parseRange(value);
25
+ default: return null;
26
+ }
27
+ };
28
+ /** Validate untrusted event data and copy only the documented fields. */
29
+ export const parseLumenChartDatumActivation = (value) => {
30
+ if (!isRecord(value))
31
+ return null;
32
+ if ('datumId' in value && typeof value.datumId !== 'string')
33
+ return null;
34
+ const detail = parseDatum(value);
35
+ if (!detail)
36
+ return null;
37
+ return typeof value.datumId === 'string' ? { ...detail, datumId: value.datumId } : detail;
38
+ };
39
+ export const createLumenChartDatumActivation = (seriesId, datum) => parseLumenChartDatumActivation({
40
+ ...(datum.id === undefined ? {} : { datumId: datum.id }),
41
+ kind: 'series',
42
+ seriesId,
43
+ x: datum.x,
44
+ y: datum.y
45
+ });
46
+ export const createLumenHeatmapDatumActivation = (datum) => parseLumenChartDatumActivation({
47
+ ...(datum.id === undefined ? {} : { datumId: datum.id }),
48
+ kind: 'heatmap',
49
+ value: datum.value,
50
+ x: datum.x,
51
+ y: datum.y
52
+ });
53
+ export const createLumenRangeDatumActivation = (datum) => parseLumenChartDatumActivation({
54
+ ...(datum.id === undefined ? {} : { datumId: datum.id }),
55
+ high: datum.high,
56
+ kind: 'range',
57
+ low: datum.low,
58
+ x: datum.x
59
+ });
60
+ const ownedActivationTarget = (root, target) => {
61
+ const ElementConstructor = root.ownerDocument.defaultView?.Element;
62
+ if (!ElementConstructor || !(target instanceof ElementConstructor))
63
+ return null;
64
+ const element = target.closest('[data-ui-chart-datum]');
65
+ if (element?.closest('[data-ui-chart-activation]') !== root)
66
+ return null;
67
+ if (element.closest('[inert], [hidden], [disabled], [aria-disabled="true"]'))
68
+ return null;
69
+ return element;
70
+ };
71
+ const parseSerializedDatum = (serialized) => {
72
+ try {
73
+ const value = JSON.parse(serialized);
74
+ return parseLumenChartDatumActivation(value);
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ };
80
+ /** Native buttons provide the keyboard path; decorative SVG marks use the same click path. */
81
+ export const createLumenChartActivationController = (root) => {
82
+ const activate = (event) => {
83
+ if (event.defaultPrevented || event.button !== 0 || !root.isConnected)
84
+ return;
85
+ const target = ownedActivationTarget(root, event.target);
86
+ const serialized = target?.getAttribute('data-ui-chart-datum');
87
+ if (!serialized)
88
+ return;
89
+ const detail = parseSerializedDatum(serialized);
90
+ if (detail)
91
+ root.dispatchEvent(new CustomEvent('ui:chart-datum-activate', { bubbles: true, composed: true, detail }));
92
+ };
93
+ root.addEventListener('click', activate);
94
+ return { destroy: () => {
95
+ root.removeEventListener('click', activate);
96
+ } };
97
+ };
@@ -0,0 +1,12 @@
1
+ export interface LumenChartCursorChangeDetail {
2
+ x: number | string | null;
3
+ }
4
+ export type LumenChartCursorChangeEvent = CustomEvent<LumenChartCursorChangeDetail>;
5
+ export interface LumenChartInteractionController {
6
+ destroy: () => void;
7
+ select: (x: number | string | null) => void;
8
+ setControlledCursor: (x: number | string | null | undefined) => void;
9
+ }
10
+ /** Optional DOM enhancement shared by web adapters; no global listeners or application data transport. */
11
+ export declare const createLumenChartInteractionController: (root: HTMLElement) => LumenChartInteractionController;
12
+ //# sourceMappingURL=chart-interaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-interaction.d.ts","sourceRoot":"","sources":["../src/chart-interaction.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,4BAA4B;IAC3C,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,MAAM,2BAA2B,GAAG,WAAW,CAAC,4BAA4B,CAAC,CAAA;AAEnF,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC3C,mBAAmB,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,IAAI,CAAA;CACrE;AA4ED,0GAA0G;AAC1G,eAAO,MAAM,qCAAqC,GAAI,MAAM,WAAW,KAAG,+BAqSzE,CAAA"}