@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
@@ -0,0 +1,16 @@
1
+ export interface WorldMapZoomAnchor {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ interface MapBounds extends WorldMapZoomAnchor {
6
+ width: number;
7
+ height: number;
8
+ }
9
+ /** Map geometry uses a left origin; RTL browser scroll offsets use the right edge. */
10
+ export declare const readMapScrollLeft: (viewport: HTMLElement) => number;
11
+ export declare const writeMapScrollLeft: (viewport: HTMLElement, value: number) => void;
12
+ export declare const getHighlightedMapBounds: (root: HTMLElement) => MapBounds | undefined;
13
+ export declare const fitHighlightedMap: (root: HTMLElement, viewport: HTMLElement, update: (zoom: number) => void) => void;
14
+ export declare const initMapWheelZoom: (viewport: HTMLElement, getZoom: () => number, update: (value: number, anchor: WorldMapZoomAnchor) => void, signal: AbortSignal) => void;
15
+ export {};
16
+ //# sourceMappingURL=world-map-navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-map-navigation.d.ts","sourceRoot":"","sources":["../src/world-map-navigation.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,UAAU,SAAU,SAAQ,kBAAkB;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAID,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,GAAI,UAAU,WAAW,KAAG,MAMzD,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,UAAU,WAAW,EAAE,OAAO,MAAM,KAAG,IAIzE,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAI,MAAM,WAAW,KAAG,SAAS,GAAG,SAwBvE,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,MAAM,WAAW,EAAE,UAAU,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KACvE,IAeF,CAAA;AAED,eAAO,MAAM,gBAAgB,GAC3B,UAAU,WAAW,EAAE,SAAS,MAAM,MAAM,EAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,KAAK,IAAI,EAAE,QAAQ,WAAW,KAC/E,IAYF,CAAA"}
@@ -0,0 +1,53 @@
1
+ const isRtlMap = (viewport) => viewport.ownerDocument.defaultView?.getComputedStyle(viewport).direction === 'rtl';
2
+ /** Map geometry uses a left origin; RTL browser scroll offsets use the right edge. */
3
+ export const readMapScrollLeft = (viewport) => {
4
+ if (!isRtlMap(viewport))
5
+ return viewport.scrollLeft;
6
+ const max = Math.max(0, viewport.scrollWidth - viewport.clientWidth);
7
+ return Math.min(max, Math.max(0, max + viewport.scrollLeft));
8
+ };
9
+ export const writeMapScrollLeft = (viewport, value) => {
10
+ const max = Math.max(0, viewport.scrollWidth - viewport.clientWidth);
11
+ viewport.scrollLeft = isRtlMap(viewport) ? Math.min(max, Math.max(0, value)) - max : Math.max(0, value);
12
+ };
13
+ export const getHighlightedMapBounds = (root) => {
14
+ const paths = [...root.querySelectorAll('.ui-world-map__country--highlighted')];
15
+ let bounds;
16
+ for (const path of paths) {
17
+ const box = path.getBBox();
18
+ if (![box.x, box.y, box.width, box.height].every(Number.isFinite))
19
+ continue;
20
+ if (!bounds) {
21
+ bounds = { x: box.x, y: box.y, width: box.width, height: box.height };
22
+ continue;
23
+ }
24
+ const right = Math.max(bounds.x + bounds.width, box.x + box.width);
25
+ const bottom = Math.max(bounds.y + bounds.height, box.y + box.height);
26
+ const x = Math.min(bounds.x, box.x);
27
+ const y = Math.min(bounds.y, box.y);
28
+ bounds = { x, y, width: right - x, height: bottom - y };
29
+ }
30
+ return bounds;
31
+ };
32
+ export const fitHighlightedMap = (root, viewport, update) => {
33
+ const bounds = getHighlightedMapBounds(root);
34
+ if (!bounds)
35
+ return;
36
+ const scaleToBounds = Math.min(1000 / Math.max(1, bounds.width), 400 / Math.max(1, bounds.height));
37
+ const zoom = Math.min(8, Math.max(1, scaleToBounds * 0.85));
38
+ update(zoom);
39
+ const scale = viewport.clientWidth * zoom / 1000;
40
+ writeMapScrollLeft(viewport, (bounds.x + bounds.width / 2) * scale - viewport.clientWidth / 2);
41
+ viewport.scrollTop = Math.max(0, (bounds.y + bounds.height / 2) * scale - viewport.clientHeight / 2);
42
+ };
43
+ export const initMapWheelZoom = (viewport, getZoom, update, signal) => {
44
+ viewport.addEventListener('wheel', event => {
45
+ if (!event.ctrlKey && !event.metaKey)
46
+ return;
47
+ event.preventDefault();
48
+ const rect = viewport.getBoundingClientRect();
49
+ const unit = [1, 16, viewport.clientHeight][event.deltaMode] ?? 1;
50
+ const delta = Math.max(-1000, Math.min(1000, event.deltaY * unit));
51
+ update(getZoom() * Math.exp(-delta * 0.002), { x: event.clientX - rect.left, y: event.clientY - rect.top });
52
+ }, { passive: false, signal });
53
+ };
@@ -0,0 +1,3 @@
1
+ /** Mouse and pen dragging complements native touch and keyboard scrolling. */
2
+ export declare const initLumenWorldMapPan: (viewport: HTMLElement, signal: AbortSignal) => void;
3
+ //# sourceMappingURL=world-map-pan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-map-pan.d.ts","sourceRoot":"","sources":["../src/world-map-pan.ts"],"names":[],"mappings":"AASA,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,GAAI,UAAU,WAAW,EAAE,QAAQ,WAAW,KAAG,IAsFjF,CAAA"}
@@ -0,0 +1,66 @@
1
+ /** Mouse and pen dragging complements native touch and keyboard scrolling. */
2
+ export const initLumenWorldMapPan = (viewport, signal) => {
3
+ let drag;
4
+ let suppressClick = false;
5
+ let clickTimer;
6
+ const end = () => {
7
+ if (!drag)
8
+ return;
9
+ suppressClick = drag.moved;
10
+ if (viewport.hasPointerCapture(drag.id))
11
+ viewport.releasePointerCapture(drag.id);
12
+ drag = undefined;
13
+ viewport.removeAttribute('data-panning');
14
+ clearTimeout(clickTimer);
15
+ clickTimer = setTimeout(() => {
16
+ suppressClick = false;
17
+ }, 0);
18
+ };
19
+ viewport.addEventListener('pointerdown', event => {
20
+ if (event.button !== 0 || event.pointerType === 'touch' || viewport.dataset.panEnabled !== 'true')
21
+ return;
22
+ drag = {
23
+ id: event.pointerId,
24
+ x: event.clientX,
25
+ y: event.clientY,
26
+ left: viewport.scrollLeft,
27
+ top: viewport.scrollTop,
28
+ moved: false
29
+ };
30
+ }, { signal });
31
+ viewport.addEventListener('pointerleave', () => {
32
+ if (!drag?.moved)
33
+ drag = undefined;
34
+ }, { signal });
35
+ viewport.addEventListener('pointermove', event => {
36
+ if (drag?.id !== event.pointerId)
37
+ return;
38
+ const x = event.clientX - drag.x;
39
+ const y = event.clientY - drag.y;
40
+ if (!drag.moved && Math.hypot(x, y) < 4)
41
+ return;
42
+ drag.moved = true;
43
+ viewport.setPointerCapture(drag.id);
44
+ viewport.dataset.panning = '';
45
+ viewport.scrollLeft = drag.left - x;
46
+ viewport.scrollTop = drag.top - y;
47
+ event.preventDefault();
48
+ }, { signal });
49
+ viewport.addEventListener('pointerup', end, { signal });
50
+ viewport.addEventListener('pointercancel', end, { signal });
51
+ viewport.addEventListener('lostpointercapture', () => {
52
+ drag = undefined;
53
+ viewport.removeAttribute('data-panning');
54
+ }, { signal });
55
+ viewport.addEventListener('click', event => {
56
+ if (!suppressClick)
57
+ return;
58
+ event.preventDefault();
59
+ event.stopImmediatePropagation();
60
+ suppressClick = false;
61
+ }, { capture: true, signal });
62
+ signal.addEventListener('abort', () => {
63
+ end();
64
+ clearTimeout(clickTimer);
65
+ }, { once: true });
66
+ };
@@ -0,0 +1,13 @@
1
+ /** Shared DOM enhancement for the web map's native zoom controls and scrollable viewport. */
2
+ export interface LumenWorldMapZoomLabels {
3
+ fit: string;
4
+ level: string;
5
+ reset: string;
6
+ viewport: string;
7
+ zoomIn: string;
8
+ zoomOut: string;
9
+ }
10
+ export declare const lumenWorldMapZoomLabels: Readonly<LumenWorldMapZoomLabels>;
11
+ export declare const normalizeLumenWorldMapZoom: (value: number) => number;
12
+ export declare const initLumenWorldMapZoom: (root: HTMLElement, signal: AbortSignal) => void;
13
+ //# sourceMappingURL=world-map-zoom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-map-zoom.d.ts","sourceRoot":"","sources":["../src/world-map-zoom.ts"],"names":[],"mappings":"AAGA,6FAA6F;AAC7F,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,uBAAuB,CAEpE,CAAA;AAEF,eAAO,MAAM,0BAA0B,GAAI,OAAO,MAAM,KAAG,MAExD,CAAA;AAQH,eAAO,MAAM,qBAAqB,GAAI,MAAM,WAAW,EAAE,QAAQ,WAAW,KAAG,IAuE9E,CAAA"}
@@ -0,0 +1,61 @@
1
+ import { fitHighlightedMap, initMapWheelZoom, readMapScrollLeft, writeMapScrollLeft } from './world-map-navigation.js';
2
+ import { initLumenWorldMapPan } from './world-map-pan.js';
3
+ export const lumenWorldMapZoomLabels = Object.freeze({
4
+ fit: 'Fit highlighted countries', level: 'Zoom level', reset: 'Reset zoom', viewport: 'Map viewport', zoomIn: 'Zoom in', zoomOut: 'Zoom out'
5
+ });
6
+ export const normalizeLumenWorldMapZoom = (value) => Number.isFinite(value) ?
7
+ Math.min(8, Math.max(1, value)) :
8
+ 1;
9
+ const isZoomControlDisabled = (root, action, zoom) => {
10
+ if (action === 'fit')
11
+ return !root.querySelector('.ui-world-map__country--highlighted');
12
+ return action === 'in' ? zoom === 8 : zoom === 1;
13
+ };
14
+ export const initLumenWorldMapZoom = (root, signal) => {
15
+ const viewport = root.querySelector('[data-ui-world-map-viewport]');
16
+ const controls = [...root.querySelectorAll('[data-ui-world-map-zoom]')];
17
+ const status = root.querySelector('[data-ui-world-map-zoom-status]');
18
+ if (!viewport)
19
+ return;
20
+ viewport.style.setProperty('--ui-world-map-zoom', '1');
21
+ viewport.dataset.panEnabled = 'false';
22
+ viewport.scrollLeft = 0;
23
+ viewport.scrollTop = 0;
24
+ if (controls.length === 0)
25
+ return;
26
+ initLumenWorldMapPan(viewport, signal);
27
+ let zoom = 1;
28
+ const update = (value, anchor = { x: viewport.clientWidth / 2, y: viewport.clientHeight / 2 }) => {
29
+ const next = normalizeLumenWorldMapZoom(value);
30
+ const ratio = next / zoom;
31
+ const x = (readMapScrollLeft(viewport) + anchor.x) * ratio - anchor.x;
32
+ const y = (viewport.scrollTop + anchor.y) * ratio - anchor.y;
33
+ zoom = next;
34
+ viewport.dataset.panEnabled = String(zoom > 1);
35
+ viewport.style.setProperty('--ui-world-map-zoom', String(zoom));
36
+ writeMapScrollLeft(viewport, x);
37
+ viewport.scrollTop = Math.max(0, y);
38
+ if (status)
39
+ status.value = `${Math.round(zoom * 100)}%`;
40
+ for (const button of controls) {
41
+ const action = button.dataset.uiWorldMapZoom;
42
+ button.disabled = isZoomControlDisabled(root, action, zoom);
43
+ button.classList.toggle('ui-button--disabled', button.disabled);
44
+ }
45
+ };
46
+ for (const button of controls) {
47
+ button.addEventListener('click', () => {
48
+ const action = button.dataset.uiWorldMapZoom;
49
+ if (action === 'fit') {
50
+ fitHighlightedMap(root, viewport, update);
51
+ return;
52
+ }
53
+ const next = action === 'reset' ? 1 : zoom + (action === 'in' ? 0.5 : -0.5);
54
+ update(next);
55
+ }, { signal });
56
+ }
57
+ update(1);
58
+ initMapWheelZoom(viewport, () => zoom, update, signal);
59
+ if (root.dataset.initialView === 'highlighted')
60
+ fitHighlightedMap(root, viewport, update);
61
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared, framework-agnostic contract for the WorldMap primitive: variant and marker types,
3
+ * the equirectangular projection, and validation/normalization helpers used by every adapter.
4
+ *
5
+ * Country path geometry (the generated SVG data) is intentionally NOT exported from this module;
6
+ * it lives in the explicit `@santi020k/lumen-core/world-map-data` subpath so consumers who do not
7
+ * render a world map never pull the geometry payload into their bundle.
8
+ */
9
+ export type LumenWorldMapVariant = 'dotted' | 'solid';
10
+ /** One resolved country's projected SVG path and label anchor. Produced by `generate:world-map-data`. */
11
+ export interface LumenWorldMapCountryGeometry {
12
+ readonly id: string;
13
+ readonly labelX: number;
14
+ readonly labelY: number;
15
+ readonly name: string;
16
+ readonly path: string;
17
+ }
18
+ /** A consumer-supplied point of interest, placed with the same projection as country paths. */
19
+ export interface LumenWorldMapMarker {
20
+ readonly id: string;
21
+ readonly label: string;
22
+ readonly latitude: number;
23
+ readonly longitude: number;
24
+ }
25
+ export interface LumenWorldMapSelectDetail {
26
+ readonly countryId: string;
27
+ readonly highlighted: boolean;
28
+ readonly label: string;
29
+ }
30
+ export interface LumenWorldMapViewBox {
31
+ readonly height: number;
32
+ readonly width: number;
33
+ }
34
+ export interface LumenWorldMapPoint {
35
+ readonly x: number;
36
+ readonly y: number;
37
+ }
38
+ /** Fixed SVG viewBox shared by every projected country path and marker. */
39
+ export declare const LUMEN_WORLD_MAP_VIEW_BOX: LumenWorldMapViewBox;
40
+ /**
41
+ * Latitude band kept by the equirectangular projection. Antarctica is excluded from the dataset
42
+ * for a familiar world-map extent; see maps/README.md for the documented rationale.
43
+ */
44
+ export declare const LUMEN_WORLD_MAP_LATITUDE_BOUNDS: Readonly<{
45
+ max: 84;
46
+ min: -60;
47
+ }>;
48
+ /**
49
+ * Projects a [longitude, latitude] pair to the shared SVG viewBox using a simple equirectangular
50
+ * projection. This is the single documented projection used to build country paths
51
+ * (`generate:world-map-data`) and to place markers at render time; keep both in sync with
52
+ * scripts/lib/world-map-geometry.mjs if either changes.
53
+ */
54
+ export declare const projectLumenWorldMapCoordinate: (longitude: number, latitude: number) => LumenWorldMapPoint;
55
+ export declare const normalizeLumenWorldMapCountries: (value: unknown) => LumenWorldMapCountryGeometry[];
56
+ /**
57
+ * Trims, upper-cases, deduplicates, and drops highlighted country ids that are not present in
58
+ * `countries`. Invalid entries (wrong type, blank, unknown, duplicate) are silently dropped so a
59
+ * typo in application data degrades gracefully instead of breaking the map.
60
+ */
61
+ export declare const normalizeLumenWorldMapHighlightedCountries: (highlighted: unknown, countries: readonly LumenWorldMapCountryGeometry[]) => string[];
62
+ /**
63
+ * Validates consumer-supplied markers: requires a non-blank id/label, finite latitude/longitude
64
+ * within the visible latitude band, and drops duplicate ids. Invalid markers are dropped rather than throwing.
65
+ */
66
+ export declare const normalizeLumenWorldMapMarkers: (markers: unknown) => LumenWorldMapMarker[];
67
+ export declare const findLumenWorldMapCountry: (countries: readonly LumenWorldMapCountryGeometry[], countryId: string | null | undefined) => LumenWorldMapCountryGeometry | undefined;
68
+ /** Resolves a country's display label, honoring a consumer-supplied override keyed by country id. */
69
+ export declare const resolveLumenWorldMapCountryLabel: (country: LumenWorldMapCountryGeometry, labelOverrides?: Readonly<Record<string, string>> | null) => string;
70
+ export declare const createLumenWorldMapSelectDetail: (country: LumenWorldMapCountryGeometry, highlightedIds: ReadonlySet<string>, labelOverrides?: Readonly<Record<string, string>> | null) => LumenWorldMapSelectDetail;
71
+ //# sourceMappingURL=world-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-map.d.ts","sourceRoot":"","sources":["../src/world-map.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,OAAO,CAAA;AAErD,yGAAyG;AACzG,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,+FAA+F;AAC/F,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB,EAAE,oBAAkE,CAAA;AAEzG;;;GAGG;AACH,eAAO,MAAM,+BAA+B;;;EAAuC,CAAA;AAEnF;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GACzC,WAAW,MAAM,EACjB,UAAU,MAAM,KACf,kBAgBF,CAAA;AAsBD,eAAO,MAAM,+BAA+B,GAAI,OAAO,OAAO,KAAG,4BAA4B,EAkB5F,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,0CAA0C,GACrD,aAAa,OAAO,EACpB,WAAW,SAAS,4BAA4B,EAAE,KACjD,MAAM,EAqBR,CAAA;AA2BD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACxC,SAAS,OAAO,KACf,mBAAmB,EAkBrB,CAAA;AAED,eAAO,MAAM,wBAAwB,GACnC,WAAW,SAAS,4BAA4B,EAAE,EAClD,WAAW,MAAM,GAAG,IAAI,GAAG,SAAS,KACnC,4BAA4B,GAAG,SAMjC,CAAA;AAED,qGAAqG;AACrG,eAAO,MAAM,gCAAgC,GAC3C,SAAS,4BAA4B,EACrC,iBAAiB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,KACvD,MAIF,CAAA;AAED,eAAO,MAAM,+BAA+B,GAC1C,SAAS,4BAA4B,EACrC,gBAAgB,WAAW,CAAC,MAAM,CAAC,EACnC,iBAAiB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,KACvD,yBAID,CAAA"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Shared, framework-agnostic contract for the WorldMap primitive: variant and marker types,
3
+ * the equirectangular projection, and validation/normalization helpers used by every adapter.
4
+ *
5
+ * Country path geometry (the generated SVG data) is intentionally NOT exported from this module;
6
+ * it lives in the explicit `@santi020k/lumen-core/world-map-data` subpath so consumers who do not
7
+ * render a world map never pull the geometry payload into their bundle.
8
+ */
9
+ /** Fixed SVG viewBox shared by every projected country path and marker. */
10
+ export const LUMEN_WORLD_MAP_VIEW_BOX = Object.freeze({ height: 400, width: 1000 });
11
+ /**
12
+ * Latitude band kept by the equirectangular projection. Antarctica is excluded from the dataset
13
+ * for a familiar world-map extent; see maps/README.md for the documented rationale.
14
+ */
15
+ export const LUMEN_WORLD_MAP_LATITUDE_BOUNDS = Object.freeze({ max: 84, min: -60 });
16
+ /**
17
+ * Projects a [longitude, latitude] pair to the shared SVG viewBox using a simple equirectangular
18
+ * projection. This is the single documented projection used to build country paths
19
+ * (`generate:world-map-data`) and to place markers at render time; keep both in sync with
20
+ * scripts/lib/world-map-geometry.mjs if either changes.
21
+ */
22
+ export const projectLumenWorldMapCoordinate = (longitude, latitude) => {
23
+ if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
24
+ throw new RangeError(`Expected a finite [longitude, latitude] pair, received [${longitude}, ${latitude}].`);
25
+ }
26
+ if (longitude < -180 || longitude > 180 || latitude < -90 || latitude > 90) {
27
+ throw new RangeError('Longitude must be between -180 and 180 and latitude between -90 and 90.');
28
+ }
29
+ const { max: latitudeMax, min: latitudeMin } = LUMEN_WORLD_MAP_LATITUDE_BOUNDS;
30
+ const { height, width } = LUMEN_WORLD_MAP_VIEW_BOX;
31
+ return {
32
+ x: ((longitude + 180) / 360) * width,
33
+ y: ((latitudeMax - latitude) / (latitudeMax - latitudeMin)) * height
34
+ };
35
+ };
36
+ const isPlainString = (value) => typeof value === 'string' && value.trim().length > 0;
37
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
38
+ const finiteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
39
+ const parseCountry = (value) => {
40
+ if (!isRecord(value))
41
+ return undefined;
42
+ const { id, labelX, labelY, name, path } = value;
43
+ if (typeof id !== 'string' || typeof name !== 'string')
44
+ return undefined;
45
+ if (!isPlainString(path))
46
+ return undefined;
47
+ if (!finiteNumber(labelX) || !finiteNumber(labelY))
48
+ return undefined;
49
+ if (!id.trim() || !name.trim())
50
+ return undefined;
51
+ return { id: id.trim().toUpperCase(), labelX, labelY, name, path };
52
+ };
53
+ export const normalizeLumenWorldMapCountries = (value) => {
54
+ if (!Array.isArray(value))
55
+ return [];
56
+ const candidates = value;
57
+ const result = [];
58
+ const ids = new Set();
59
+ for (const candidate of candidates) {
60
+ const country = parseCountry(candidate);
61
+ if (!country || ids.has(country.id))
62
+ continue;
63
+ ids.add(country.id);
64
+ result.push(country);
65
+ }
66
+ return result;
67
+ };
68
+ /**
69
+ * Trims, upper-cases, deduplicates, and drops highlighted country ids that are not present in
70
+ * `countries`. Invalid entries (wrong type, blank, unknown, duplicate) are silently dropped so a
71
+ * typo in application data degrades gracefully instead of breaking the map.
72
+ */
73
+ export const normalizeLumenWorldMapHighlightedCountries = (highlighted, countries) => {
74
+ if (!Array.isArray(highlighted))
75
+ return [];
76
+ const candidates = highlighted;
77
+ const knownIds = new Set(countries.map(country => country.id));
78
+ const seen = new Set();
79
+ const result = [];
80
+ for (const raw of candidates) {
81
+ if (!isPlainString(raw))
82
+ continue;
83
+ const id = raw.trim().toUpperCase();
84
+ if (seen.has(id) || !knownIds.has(id))
85
+ continue;
86
+ seen.add(id);
87
+ result.push(id);
88
+ }
89
+ return result;
90
+ };
91
+ const isValidLatitude = (latitude) => {
92
+ const { max, min } = LUMEN_WORLD_MAP_LATITUDE_BOUNDS;
93
+ return latitude >= min && latitude <= max;
94
+ };
95
+ const isValidLongitude = (longitude) => longitude >= -180 && longitude <= 180;
96
+ /** Parses one candidate marker, or returns undefined if it fails any validation rule. */
97
+ const parseLumenWorldMapMarker = (marker) => {
98
+ if (!isRecord(marker) || !isPlainString(marker.id) || !isPlainString(marker.label))
99
+ return undefined;
100
+ const { latitude, longitude } = marker;
101
+ if (typeof latitude !== 'number' || typeof longitude !== 'number')
102
+ return undefined;
103
+ if (!Number.isFinite(latitude) || !Number.isFinite(longitude))
104
+ return undefined;
105
+ if (!isValidLatitude(latitude) || !isValidLongitude(longitude))
106
+ return undefined;
107
+ return { id: marker.id.trim(), label: marker.label.trim(), latitude, longitude };
108
+ };
109
+ /**
110
+ * Validates consumer-supplied markers: requires a non-blank id/label, finite latitude/longitude
111
+ * within the visible latitude band, and drops duplicate ids. Invalid markers are dropped rather than throwing.
112
+ */
113
+ export const normalizeLumenWorldMapMarkers = (markers) => {
114
+ if (!Array.isArray(markers))
115
+ return [];
116
+ const candidates = markers;
117
+ const seen = new Set();
118
+ const result = [];
119
+ for (const candidate of candidates) {
120
+ const marker = parseLumenWorldMapMarker(candidate);
121
+ if (!marker || seen.has(marker.id))
122
+ continue;
123
+ seen.add(marker.id);
124
+ result.push(marker);
125
+ }
126
+ return result;
127
+ };
128
+ export const findLumenWorldMapCountry = (countries, countryId) => {
129
+ if (!isPlainString(countryId))
130
+ return undefined;
131
+ const id = countryId.trim().toUpperCase();
132
+ return countries.find(country => country.id === id);
133
+ };
134
+ /** Resolves a country's display label, honoring a consumer-supplied override keyed by country id. */
135
+ export const resolveLumenWorldMapCountryLabel = (country, labelOverrides) => {
136
+ const override = labelOverrides?.[country.id];
137
+ return isPlainString(override) ? override.trim() : country.name;
138
+ };
139
+ export const createLumenWorldMapSelectDetail = (country, highlightedIds, labelOverrides) => ({
140
+ countryId: country.id,
141
+ highlighted: highlightedIds.has(country.id),
142
+ label: resolveLumenWorldMapCountryLabel(country, labelOverrides)
143
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@santi020k/lumen-core",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -67,22 +67,69 @@
67
67
  "./icon-data": {
68
68
  "types": "./dist/icon-data.generated.d.ts",
69
69
  "import": "./dist/icon-data.generated.js"
70
+ },
71
+ "./image-comparison": {
72
+ "types": "./dist/image-comparison.d.ts",
73
+ "import": "./dist/image-comparison.js"
74
+ },
75
+ "./virtual-list": {
76
+ "types": "./dist/virtual-list.d.ts",
77
+ "import": "./dist/virtual-list.js"
78
+ },
79
+ "./virtual-window": {
80
+ "types": "./dist/virtual-window.d.ts",
81
+ "import": "./dist/virtual-window.js"
82
+ },
83
+ "./rive": {
84
+ "types": "./dist/rive.d.ts",
85
+ "import": "./dist/rive.js"
86
+ },
87
+ "./world-map": {
88
+ "types": "./dist/world-map.d.ts",
89
+ "import": "./dist/world-map.js"
90
+ },
91
+ "./world-map-data": {
92
+ "types": "./dist/world-map-data.generated.d.ts",
93
+ "import": "./dist/world-map-data.generated.js"
94
+ },
95
+ "./world-map-zoom": {
96
+ "types": "./dist/world-map-zoom.d.ts",
97
+ "import": "./dist/world-map-zoom.js"
98
+ },
99
+ "./media-workspace": {
100
+ "types": "./dist/media-workspace.d.ts",
101
+ "import": "./dist/media-workspace.js"
102
+ },
103
+ "./media-viewport": {
104
+ "types": "./dist/media-viewport.d.ts",
105
+ "import": "./dist/media-viewport.js"
70
106
  }
71
107
  },
72
108
  "files": [
73
109
  "dist",
74
110
  "README.md",
75
111
  "LICENSE",
112
+ "PHONE_FLAG_LICENSE.txt",
76
113
  "THIRD_PARTY_NOTICES.md"
77
114
  ],
78
115
  "dependencies": {
79
- "@lucide/icons": "^1.48.0",
116
+ "@lucide/icons": "^1.50.0",
80
117
  "libphonenumber-js": "^1.13.14"
81
118
  },
82
119
  "devDependencies": {
83
- "@vitest/coverage-v8": "^5.0.1",
120
+ "@rive-app/canvas": "2.44.0",
121
+ "@vitest/coverage-v8": "^5.0.3",
122
+ "jsdom": "^30.1.1",
84
123
  "typescript": "~6.0.3",
85
- "vitest": "^5.0.1"
124
+ "vitest": "^5.0.3"
125
+ },
126
+ "peerDependencies": {
127
+ "@rive-app/canvas": "^2.44.0"
128
+ },
129
+ "peerDependenciesMeta": {
130
+ "@rive-app/canvas": {
131
+ "optional": true
132
+ }
86
133
  },
87
134
  "engines": {
88
135
  "node": ">=22.12.0"