@santi020k/lumen-astro 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 (103) hide show
  1. package/README.md +403 -7
  2. package/components/AmountField.astro +21 -0
  3. package/components/AnimatedNumber.astro +1 -1
  4. package/components/ApprovalCard.astro +30 -0
  5. package/components/AttachmentList.astro +15 -0
  6. package/components/AttachmentPreview.astro +51 -0
  7. package/components/Avatar.astro +2 -0
  8. package/components/BarChart.astro +95 -31
  9. package/components/BoxPlot.astro +77 -0
  10. package/components/Breadcrumb.astro +2 -1
  11. package/components/BulletChart.astro +64 -0
  12. package/components/Button.astro +1 -1
  13. package/components/Calendar.astro +54 -65
  14. package/components/CalendarHeatmap.astro +76 -0
  15. package/components/Card.astro +4 -0
  16. package/components/ChangeSummary.astro +36 -0
  17. package/components/ChartDatumActions.astro +24 -0
  18. package/components/ChartMotion.astro +11 -0
  19. package/components/Code.astro +18 -3
  20. package/components/CodeTabs.astro +12 -0
  21. package/components/ComboChart.astro +93 -11
  22. package/components/CountryFlag.astro +23 -0
  23. package/components/DataTable.astro +15 -7
  24. package/components/DatePicker.astro +24 -17
  25. package/components/DescriptionDetail.astro +18 -0
  26. package/components/DescriptionItem.astro +18 -0
  27. package/components/DescriptionTerm.astro +18 -0
  28. package/components/DeviceFrame.astro +79 -0
  29. package/components/DialogBody.astro +20 -0
  30. package/components/DialogClose.astro +21 -0
  31. package/components/DialogFooter.astro +20 -0
  32. package/components/DialogHeader.astro +20 -0
  33. package/components/DialogTitle.astro +21 -0
  34. package/components/DumbbellChart.astro +8 -0
  35. package/components/FileUpload.astro +3 -1
  36. package/components/FilterBar.astro +25 -0
  37. package/components/FunnelChart.astro +50 -0
  38. package/components/Grid.astro +2 -2
  39. package/components/Heatmap.astro +61 -12
  40. package/components/Histogram.astro +30 -0
  41. package/components/Icon.astro +7 -3
  42. package/components/ImageComparison.astro +74 -0
  43. package/components/Input.astro +2 -2
  44. package/components/LineChart.astro +127 -67
  45. package/components/LollipopChart.astro +8 -0
  46. package/components/MediaFilmstrip.astro +19 -0
  47. package/components/MediaThumbnail.astro +29 -0
  48. package/components/MediaViewport.astro +38 -0
  49. package/components/Mentions.astro +2 -1
  50. package/components/MessageScroller.astro +6 -0
  51. package/components/MotionGroup.astro +19 -0
  52. package/components/NativeSelect.astro +2 -2
  53. package/components/Pagination.astro +2 -1
  54. package/components/PasswordField.astro +1 -1
  55. package/components/PhoneInput.astro +60 -9
  56. package/components/PhoneNumber.astro +19 -0
  57. package/components/PieChart.astro +26 -3
  58. package/components/PromptComposer.astro +36 -0
  59. package/components/RangeChart.astro +38 -8
  60. package/components/ScatterChart.astro +62 -14
  61. package/components/Segmented.astro +5 -5
  62. package/components/Select.astro +8 -8
  63. package/components/SourceCitation.astro +18 -0
  64. package/components/Sparkline.astro +9 -10
  65. package/components/Stack.astro +2 -2
  66. package/components/StreamMessage.astro +26 -0
  67. package/components/Tabs.astro +3 -0
  68. package/components/ToolActivity.astro +17 -0
  69. package/components/TreeSelect.astro +1 -0
  70. package/components/VirtualList.astro +5 -2
  71. package/components/VisualEffect.astro +21 -0
  72. package/components/WaterfallChart.astro +27 -0
  73. package/components/WorldMap.astro +213 -0
  74. package/forms.ts +17 -4
  75. package/index.ts +37 -0
  76. package/internal/ChartInspection.astro +27 -0
  77. package/internal/ComparisonChart.astro +69 -0
  78. package/internal/IntervalChart.astro +66 -0
  79. package/package.json +6 -4
  80. package/runtime/UIPrimitives.astro +670 -647
  81. package/runtime/chart-actions.ts +19 -0
  82. package/runtime/controllers/amount-field.ts +12 -0
  83. package/runtime/controllers/attachments.ts +13 -0
  84. package/runtime/controllers/chart-activation.ts +33 -0
  85. package/runtime/controllers/charts.ts +33 -0
  86. package/runtime/controllers/combobox.ts +39 -0
  87. package/runtime/controllers/comparison-reset.ts +20 -0
  88. package/runtime/controllers/data-table.ts +142 -0
  89. package/runtime/controllers/dialogs.ts +32 -32
  90. package/runtime/controllers/document-navigation.ts +54 -22
  91. package/runtime/controllers/file-upload.ts +89 -0
  92. package/runtime/controllers/image-comparison.ts +53 -0
  93. package/runtime/controllers/media-selection.ts +20 -0
  94. package/runtime/controllers/media-viewport.ts +54 -0
  95. package/runtime/controllers/media-workspace.ts +8 -0
  96. package/runtime/controllers/message-scroller.ts +13 -0
  97. package/runtime/controllers/motion.ts +15 -16
  98. package/runtime/controllers/optional-media.ts +29 -0
  99. package/runtime/controllers/phone-input.ts +128 -39
  100. package/runtime/controllers/visual-interactions.ts +75 -0
  101. package/runtime/controllers/world-map.ts +154 -0
  102. package/styles/critical.css +634 -61
  103. package/styles/lumen.css +101 -0
package/README.md CHANGED
@@ -1,10 +1,79 @@
1
- # @santi020k/lumen-astro
1
+ <p align="center">
2
+ <a href="https://lumen.santi020k.com">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/santi020k/lumen/main/docs/assets/readme/package-dark.svg">
5
+ <img src="https://raw.githubusercontent.com/santi020k/lumen/main/docs/assets/readme/package-light.svg" alt="Lumen UI — Web. Native. Thoughtfully connected." width="1200" height="184">
6
+ </picture>
7
+ </a>
8
+ </p>
9
+
10
+ <h1 align="center">Lumen UI · Astro</h1>
11
+
12
+ <p align="center">Reference components · Progressive enhancement · Standalone CSS</p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@santi020k/lumen-astro"><img src="https://img.shields.io/npm/v/@santi020k/lumen-astro?style=flat-square&color=0369a0" alt="npm version"></a>
16
+ <a href="https://github.com/santi020k/lumen/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-13967e?style=flat-square" alt="MIT license"></a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://lumen.santi020k.com/docs/frameworks/astro">Documentation</a>
21
+ ·
22
+ <a href="https://www.npmjs.com/package/@santi020k/lumen-astro">npm</a>
23
+ ·
24
+ <a href="https://github.com/santi020k/lumen/tree/main/packages/astro">Source</a>
25
+ ·
26
+ <a href="https://github.com/santi020k/lumen/issues">Issues</a>
27
+ </p>
28
+
29
+ **Package:** `@santi020k/lumen-astro`
30
+
31
+ **On this page:** [Install](#install) · [Appearance presets](#appearance-presets) · [Resources](#resources)
32
+
33
+ <details>
34
+ <summary>Browse all sections</summary>
35
+
36
+ - [Install](#install)
37
+ - [Appearance presets](#appearance-presets)
38
+ - [Compound interactive components](#compound-interactive-components)
39
+ - [Context navigation](#context-navigation)
40
+ - [Forms and Astro Actions](#forms-and-astro-actions)
41
+ - [Error states](#error-states)
42
+ - [Long-form navigation](#long-form-navigation)
43
+ - [Data visualization](#data-visualization)
44
+ - [Code tabs](#code-tabs)
45
+ - [Optimized images](#optimized-images)
46
+ - [Kanban boards](#kanban-boards)
47
+ - [Motion](#motion)
48
+ - [Glass surfaces](#glass-surfaces)
49
+ - [Semantic stat roots](#semantic-stat-roots)
50
+ - [Consumer composition recipes](#consumer-composition-recipes)
51
+ - [Exact amount fields](#exact-amount-fields)
52
+ - [Phone presentation in v4](#phone-presentation-in-v4)
53
+ - [Combobox keyboard behavior](#combobox-keyboard-behavior)
54
+ - [Content flow](#content-flow)
55
+ - [Attachment composition](#attachment-composition)
56
+ - [Chart datum actions](#chart-datum-actions)
57
+ - [Dashboard composition](#dashboard-composition)
58
+ - [Visual interactions and product blocks](#visual-interactions-and-product-blocks)
59
+ - [Device demonstrations](#device-demonstrations)
60
+ - [World map](#world-map)
61
+ - [Studio media workspace](#studio-media-workspace)
62
+ - [Resources](#resources)
63
+
64
+ </details>
2
65
 
3
66
  Production-ready Astro primitives for Lumen UI. The stylesheet is standalone CSS, so no Tailwind
4
67
  configuration is required to render the components.
5
68
 
69
+ Form controls use `visualSize` (`visual-size` in Elements) with `default`, `sm` and `lg`.
70
+ Select, PhoneInput and Segmented follow Input and NativeSelect; numeric input/select `size` keeps
71
+ its native meaning. See the [v4 migration guide](https://github.com/santi020k/lumen/blob/main/docs/migrating-v3-to-v4.md#form-control-visual-sizing).
72
+
6
73
  ## Install
7
74
 
75
+ Requires Astro 5 or newer in the consuming application.
76
+
8
77
  ```bash
9
78
  pnpm add @santi020k/lumen-astro
10
79
  ```
@@ -53,11 +122,11 @@ import UIPrimitives from '@santi020k/lumen-astro/runtime'
53
122
 
54
123
  ```astro
55
124
  ---
56
- import { Button, Card, Input } from '@santi020k/lumen-astro'
125
+ import { Button, Card, Input, Label } from '@santi020k/lumen-astro'
57
126
  ---
58
127
 
59
128
  <Card>
60
- <label for="email">Email</label>
129
+ <Label for="email">Email</Label>
61
130
  <Input id="email" type="email" placeholder="you@example.com" />
62
131
  <Button>Subscribe</Button>
63
132
  </Card>
@@ -74,7 +143,10 @@ import { Button, Card, Input } from '@santi020k/lumen-astro'
74
143
  `PhoneInput` generates localized country names, supplementary flags, and calling codes when custom
75
144
  `countries` are not provided. Mount `UIPrimitives` once to enable as-you-type formatting,
76
145
  metadata-backed validation, automatic country detection for pasted international numbers, and the
77
- `ui:phone-change` event with a `LumenPhoneNumber` detail.
146
+ `ui:phone-change` event with a `LumenPhoneNumber` detail. Validation messages and inherited locale
147
+ resolve in the phone control's owning document, including after iframe adoption. Repeated runtime
148
+ initialization binds adopted phone controls to their current reset root; reset synchronization follows
149
+ the current form owner and honors cancellation without duplicating edit listeners.
78
150
 
79
151
  The phone metadata and normalization controller are selector-loaded only when a rendered page
80
152
  contains `PhoneInput`; pages without the component do not evaluate or download that controller.
@@ -83,16 +155,33 @@ contains `PhoneInput`; pages without the component do not evaluate or download t
83
155
  <PhoneInput countryValue="CO" locale="en-US" name="hospitalPhone" />
84
156
  ```
85
157
 
158
+ ## Appearance presets
159
+
160
+ Set `data-lumen-preset="studio"` and an optional `data-lumen-scheme="light|dark"` on the application root. See [appearance presets](https://github.com/santi020k/lumen/blob/main/docs/appearance-presets.md) for Default, Studio, Glass and scoped customization.
161
+
86
162
  ## Compound interactive components
87
163
 
88
164
  Popover, dropdown menu, tabs, and tooltip parts expose the DOM and ARIA contracts consumed by the
89
165
  shared progressive-enhancement runtime. Prefer these parts over recreating roles and `data-ui-*`
90
166
  attributes by hand.
91
167
 
168
+ Disclosure keyboard navigation skips hidden or inert regions, invisible controls, and native
169
+ disabled controls, including a disabled fieldset. Available controls in a fieldset's first legend
170
+ retain their native keyboard behavior; removing `inert` makes a region available again.
171
+
172
+ Mentions keeps suggestion navigation on the textarea through `aria-activedescendant`; suggestion
173
+ buttons are excluded from the Tab sequence. Returning focus before the delayed blur dismissal
174
+ preserves reopened suggestions. Static Toast markup is enhanced again after client
175
+ navigation, retaining Escape dismissal without duplicate listeners.
176
+
92
177
  Tabs keep the selected trigger visible when a narrow tab list scrolls horizontally and emit a
93
178
  typed `ui:tabs-change` event. Import `LumenTabsChangeDetail` or `LumenTabsChangeEvent` from this
94
179
  package when application behavior follows the selected value.
95
180
 
181
+ Keyboard tab navigation skips disabled triggers and stays within the current group when tabs are nested.
182
+ Dialogs preserve native `autofocus`, restore their connected opener on close, and dismiss only a
183
+ backdrop press that starts and ends outside the dialog. Alert dialogs retain explicit dismissal.
184
+
96
185
  ```astro
97
186
  ---
98
187
  import {
@@ -211,7 +300,7 @@ announcement.
211
300
  </ErrorState>
212
301
  ```
213
302
 
214
- See the repository [Astro error-handling guide](../../docs/error-handling.md#astro) for the complete
303
+ See the repository [Astro error-handling guide](https://github.com/santi020k/lumen/blob/main/docs/error-handling.md#astro) for the complete
215
304
  example, retry ownership, announcement behavior, and verification guidance.
216
305
 
217
306
  Use `Icon` for Lucide icons by name across framework adapters.
@@ -266,6 +355,17 @@ sync with the accessible label; free-form default children remain the idle prese
266
355
 
267
356
  ## Data visualization
268
357
 
358
+ The web visualization milestone adds `WaterfallChart` for signed changes and explicit totals, and
359
+ `Histogram` for precomputed numeric bins (`frequency="density"` for unequal widths). Line charts
360
+ support explicit continuous axes, annotations, optional keyboard/pointer/touch inspection, and
361
+ synchronized cursors. Heatmaps show labeled axes, a color legend, and explicit missing cells.
362
+ See the [visualization contracts](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md) and
363
+ [interactive web example](https://lumen.santi020k.com/docs/web/data-visualization).
364
+
365
+ Set `interactive` on `LineChart` and mount `UIPrimitives` to enable inspection and legend buttons.
366
+ Static charts continue to render without client JavaScript. After moving an enhanced chart into another
367
+ document, rerun `UIPrimitives` initialization so cursor synchronization follows that document's peers.
368
+
269
369
  Use `Sparkline` beside a metric, `BarChart` for categorical comparison, `LineChart` for ordered
270
370
  trends, `PieChart` for a small part-to-whole breakdown, `ScatterChart` for numeric relationships,
271
371
  `Heatmap` for a matrix, `RangeChart` for intervals, and `ComboChart` for a shared-domain mix of bars,
@@ -291,7 +391,7 @@ const series = [{
291
391
  `variant="donut"` and also supports `variant="pie"`.
292
392
 
293
393
  Every chart includes a factual screen-reader summary by default and accepts `summary` for more
294
- useful domain context. See [data visualization](../../docs/data-visualization.md) for selection,
394
+ useful domain context. See [data visualization](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md) for selection,
295
395
  missing-data, live-data, and accessibility guidance.
296
396
 
297
397
  Lucide intentionally excludes brand marks. For GitHub and other project-specific icons, put the
@@ -395,6 +495,17 @@ content readable without JavaScript and honor reduced-motion preferences.
395
495
  <AnimatedNumber decimals={1} suffix="%" value={99.8} />
396
496
  ```
397
497
 
498
+ With the optional motion stylesheet, native `Collapsible` and `Accordion` content smoothly expands and collapses in browsers supporting
499
+ `::details-content` and `interpolate-size`. Other browsers retain the immediate native toggle.
500
+ Dialogs, drawers, sheets, menus, and toasts retain their existing shared-token transitions.
501
+
502
+ For optional insertion, removal, or state feedback, use
503
+ [`animateLumenPresence`](https://github.com/santi020k/lumen/blob/main/packages/core/README.md#presence-motion) on a DOM reference. Applications keep
504
+ ownership of state, focus, and DOM removal. Set `data-ui-motion="reduce"` on a container for a local
505
+ reduced-motion preference; the system preference is always respected.
506
+ Try dialogs, bottom drawers, and side sheets in the [motion playground](https://lumen.santi020k.com/docs/motion-playground).
507
+ System reduced motion shortens both panel and backdrop transitions.
508
+
398
509
  ## Glass surfaces
399
510
 
400
511
  Lumen includes glassmorphism tokens and reusable classes in the shared stylesheet. Cards use a
@@ -425,7 +536,292 @@ or `variant="glass"` for selective translucency.
425
536
 
426
537
  ## Consumer composition recipes
427
538
 
428
- See [consumer UI recipes](../../docs/consumer-ui-recipes.md) for static React icons, responsive
539
+ See [consumer UI recipes](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md) for static React icons, responsive
429
540
  record tables, keyboard-aware native sheets, whole-unit amount fields, adaptive editors, and
430
541
  asynchronous action states. Each recipe identifies the public primitives and the behavior that
431
542
  remains owned by the application.
543
+
544
+ ## Exact amount fields
545
+
546
+ `AmountField` formats an ASCII decimal-string `value` in the requested locale and submits a
547
+ complete ungrouped value through its named hidden input. Include `UIPrimitives` for editing,
548
+ validity and reset behavior. Domain validation and monetary units remain application-owned.
549
+
550
+ ```astro
551
+ <AmountField name="amount" locale="es-CO" value="1234.50" aria-label="Amount COP" />
552
+ <UIPrimitives />
553
+ ```
554
+
555
+ See [consumer workflows](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md#executable-consumer-workflows).
556
+
557
+ ### Accessible documentation controls
558
+
559
+ `Code` and `CodeTabs` accept `copyLabel`, `copiedLabel`, `errorLabel`, and
560
+ `codeLabel` strings. The first three customize clipboard controls and their live
561
+ success/error feedback; `codeLabel` names the keyboard-focusable region for an
562
+ unwrapped code block. A denied or unavailable clipboard reports the error label
563
+ and keeps the source available for manual selection. `CodeTabs` forwards these
564
+ labels to each code example.
565
+
566
+ `Code` with `highlighted` enhances the supplied `pre` when the Astro runtime loads.
567
+ For keyboard scrolling before JavaScript loads, give that `pre` `tabindex="0"`,
568
+ `role="region"`, and an accessible name; existing attributes are preserved.
569
+ Wrapped blocks do not add an extra tab stop.
570
+
571
+ `NavigationMenu` contains ordinary site links, so every enabled link remains in
572
+ native Tab order. Use `Menubar` or `Toolbar` for their documented composite-widget
573
+ keyboard behavior. A `ThemeToggle` with `controlled` respects the host theme at
574
+ initialization and leaves persistence and theme changes to its owner.
575
+
576
+ ### Virtual list and editor ownership
577
+
578
+ `VirtualList` displays fixed-height rows with inert spacers that retain the full scroll extent.
579
+ Rows stay mounted; use pagination when the initial DOM cost matters. Scrolling, resizing, sizing
580
+ changes and direct row changes refresh the window. Focused rows remain available. After document
581
+ adoption, rerun the standard initializer to rebind the controller to the destination document. See the
582
+ [fixed-height list contract](https://github.com/santi020k/lumen/blob/main/docs/ai-usage.md#fixed-height-virtual-lists).
583
+
584
+ External rich-text engines should handle the cancelable `ui:editor-command-request` event before
585
+ execution and use `ui:editor-command` only for completion notifications. React also supports
586
+ `useRichTextEditor({ commandHandler })`. Disable native toolbar state syncing when the external
587
+ engine owns it. See the [editor guidance](https://github.com/santi020k/lumen/blob/main/docs/ai-usage.md).
588
+
589
+ ## Phone presentation in v4
590
+
591
+ Phone inputs bundle the same offline flag artwork on every platform. The selected country shows
592
+ its flag and calling code inside one continuous input border. Country names remain in the native
593
+ picker and its accessible name; flags are supplementary. Unknown flag codes fall back to text.
594
+
595
+ Astro and React `PhoneInput` accept `disabled`, `readOnly`, `required`, `errorMessage`,
596
+ `showValidationError`, and `inputProps`. Their `id` targets the number input in v4; React also
597
+ accepts `inputRef`. Web Components use `disabled`, `readonly`, `required`, `error-message`,
598
+ `show-validation-error="false"`, and `input-id`, with native input attributes on the host.
599
+ Both controls lock together and validation remains associated with the input. Astro forwards
600
+ `inputProps.form` to the number input, country picker and read-only country fallback so external
601
+ form submission and reset include every part.
602
+
603
+ The `phone-input`, `phone-country`, and `country-flag` styling parts plus `--ui-phone-height`,
604
+ `--ui-phone-padding`, and `--ui-phone-country-gap` replace consumer CSS overlays.
605
+
606
+ Astro and React also export `CountryFlag` (`regionCode`, optional `decorative`) and `PhoneNumber`
607
+ (`value: LumenPhoneNumber`, optional `link`). A telephone link is rendered only for a complete
608
+ E.164 value. Use the model returned by the phone normalizer; keep domain persistence in your app.
609
+ Artwork attribution is shipped with the core package in `PHONE_FLAG_LICENSE.txt`.
610
+
611
+ ## Combobox keyboard behavior
612
+
613
+ In v4, Combobox retains input focus and exposes its active option through `aria-activedescendant`.
614
+ Enter commits an active option; text editing and composition remain native. Escape dismisses one
615
+ nested control at a time. See the [shared keyboard contract](https://github.com/santi020k/lumen/blob/main/docs/ai-usage.md#combobox-keyboard-behavior-in-v4)
616
+ for dynamic options, controlled inputs and migration guidance.
617
+
618
+ ## Content flow
619
+
620
+ Stack and Grid own sibling spacing. Their gap accepts `related`, `group` (default) and `section`,
621
+ or canonical `none`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl` sizes. Card owns the inset and gap
622
+ between its visible parts; use `density="compact"`, `"comfortable"` (default) or `"spacious"`.
623
+ Elements uses the same names as attributes. Use a nested Stack for CardContent groups and Field
624
+ for label/control/feedback. See [content flow](https://github.com/santi020k/lumen/blob/main/docs/content-flow.md) and the
625
+ [v4 migration guide](https://github.com/santi020k/lumen/blob/main/docs/migrating-to-lumen.md) for ownership and changed explicit gaps.
626
+
627
+ ### Reading and complete compositions
628
+
629
+ Prose and Typography trim outer child margins and separate headings from preceding text.
630
+ Container gutters grow from 16px to 32px with viewport width; override `--ui-container-gutter`
631
+ when a product needs fixed gutters. Card allows interactive overflow; use AspectRatio to clip media.
632
+ Install `content-flow-header`, `content-flow-settings`, `content-flow-list` or `content-flow-actions`
633
+ with `lumen add <recipe> --target astro|react|elements`. MCP returns the same complete examples.
634
+ Connect application actions and replace sample IDs before reuse. See
635
+ [content flow](https://github.com/santi020k/lumen/blob/main/docs/content-flow.md) for composition and migration guidance.
636
+
637
+ ### Large fixed-height collections
638
+
639
+ `VirtualList` supports an empty `mode="data"` root with the public Core data controller.
640
+ Only the visible window, overscan and focused neighbors mount. Stable keys retain row identity;
641
+ applications own offscreen editing state. See [data rendering](https://github.com/santi020k/lumen/blob/main/docs/virtual-list-data.md) for
642
+ setup, lifecycle, accessibility and the mounted-mode tradeoff.
643
+
644
+ Action-error normalization ignores malformed fields and non-string messages. Enhanced forms include
645
+ externally associated native controls. Reinitialize with `LumenInitUiPrimitives(destination)` after
646
+ adopting an enhanced form into another document so blur validation follows its owner, including
647
+ controls created in another document. Timed toasts stay paused while hovered or focused.
648
+
649
+ ### Compound dialog tasks
650
+
651
+ Use `DialogHeader`, `DialogTitle`, `DialogBody`, `DialogFooter`, and `DialogClose`
652
+ for long forms. Associate the title explicitly so labeling works before enhancement.
653
+ `DialogBody` must be a direct child of `Dialog` for independent scrolling. Footer
654
+ submit actions target a form inside the body with the native `form` attribute.
655
+
656
+ ```astro
657
+ <Dialog id="record-dialog" aria-labelledby="record-title">
658
+ <DialogHeader><DialogTitle id="record-title">Edit record</DialogTitle></DialogHeader>
659
+ <DialogBody><Form id="record-form"><Input name="name" aria-label="Name" /></Form></DialogBody>
660
+ <DialogFooter>
661
+ <DialogClose variant="outline">Cancel</DialogClose>
662
+ <Button type="submit" form="record-form">Save</Button>
663
+ </DialogFooter>
664
+ </Dialog>
665
+ ```
666
+
667
+ Mount `UIPrimitives` once and open the dialog with a button carrying
668
+ `data-ui-dialog-trigger="record-dialog"`. Set `selectedFilesLabel="{count} files selected"`
669
+ on `FileUpload` to supply localized multiple-file feedback; the single-file label is its filename.
670
+ A trigger ignores its own click when that click arrives canceled (`event.preventDefault()`) or
671
+ when the trigger matches `:disabled` or `[aria-disabled="true"]`, matching close control behavior.
672
+ Register cancellation listeners before the runtime binds the trigger. Activating a trigger for an
673
+ already open dialog preserves its mode and original focus-return target.
674
+
675
+ ### Rich description rows
676
+
677
+ Use `Descriptions` with `DescriptionItem`, `DescriptionTerm`, and `DescriptionDetail`
678
+ when a value contains links, badges, formatted dates, or actions. Astro renders a
679
+ native `dl` containing `div`, `dt`, and `dd` elements. The existing `items` array
680
+ remains available and can be combined with child rows.
681
+
682
+ ```astro
683
+ <Descriptions>
684
+ <DescriptionItem>
685
+ <DescriptionTerm>Status</DescriptionTerm>
686
+ <DescriptionDetail><Badge variant="success">Active</Badge></DescriptionDetail>
687
+ </DescriptionItem>
688
+ </Descriptions>
689
+ ```
690
+
691
+ ## Attachment composition
692
+
693
+ Use `AttachmentList` to group native `li` children and `AttachmentPreview` for browser-owned images
694
+ with localized loading, error, and unsupported-file states. Compose independent actions rather
695
+ than nesting controls inside a linked Attachment. The application retains file validation,
696
+ authorization, persistence, and object URL cleanup. See the
697
+ [attachment composition recipe](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md#attachment-previews-and-file-lists)
698
+ for adapter props, slots, child contracts, retry identity, and safe state events.
699
+
700
+ ## Chart datum actions
701
+
702
+ Set `drilldown` on BarChart, LineChart, PieChart, ScatterChart, ComboChart, Heatmap, or RangeChart
703
+ and mount `UIPrimitives` once. Each available plotted datum has a corresponding native button in
704
+ the actions disclosure, including when `showTable={false}`. Marks remain decorative to assistive
705
+ technology. The figure emits `ui:chart-datum-activate` with a validated
706
+ `LumenChartDatumActivationDetail`; apps decide whether to navigate, filter, or open a detail view.
707
+
708
+ Translate `labels.exploreData` and `labels.formatDatumAction(context)`, and format visible values
709
+ with the chart's existing formatters. Missing observations have no action; pie actions cover only
710
+ positive slices. A line's `markers="none"` hides visible markers while retaining pointer targets
711
+ and native keyboard actions. See [data visualization](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#astro-chart-actions).
712
+
713
+ ## Dashboard composition
714
+
715
+ `FilterBar` groups host-owned filtering controls, active criteria, reset actions, and a polite
716
+ result announcement. `ChangeSummary` presents explicit before/after values and application-owned
717
+ changed state. Neither component owns requests, persistence, parsing, or financial policy.
718
+
719
+ ScatterChart supports independent X/Y formatting, explicit domains, logarithmic positive X values,
720
+ and labeled reference lines/regions. Automatic domains leave room for complete bubbles; explicit
721
+ domain limits stay exact and can intentionally crop marks. ComboChart and RangeChart include
722
+ category labels and value scales. See [consumer UI recipes](https://github.com/santi020k/lumen/blob/main/docs/consumer-ui-recipes.md)
723
+ for dashboard tables, freshness, import review, activity inbox, and persistent Kanban patterns.
724
+
725
+ ### Actual-versus-target charts
726
+
727
+ `BulletChart` compares a nullable actual `value` with a finite `target` and optional
728
+ labeled `ranges`. A strong actual bar, target marker, readable value labels, and expandable exact
729
+ data work together. Domains include zero and all measurements; invalid inputs fail closed, including malformed range containers.
730
+ Null values stay distinct from zero. See the [chart guide](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#actual-values-and-targets)
731
+ for the input, localization, and domain contracts.
732
+
733
+ ### Rankings and paired comparisons
734
+
735
+ Use `LollipopChart` for zero-based rankings and `DumbbellChart` for paired measurements (native
736
+ `LumenLollipopChart` and `LumenDumbbellChart`). Supply ordered comparison data with `id`, `label`,
737
+ nullable `value`, optional nullable `reference`, and optional `tone`. Both charts preserve missing
738
+ values and expose exact data. See the [shared visualization contract](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#rankings-and-paired-comparisons).
739
+
740
+ ### Calendars, funnels, and distributions
741
+
742
+ Use `CalendarHeatmap` for daily date-only observations with an explicit start/end range,
743
+ `FunnelChart` for ordered nonnegative stages, and `BoxPlot` for precomputed quartiles, whiskers,
744
+ and outliers. All retain missing values and expose exact data. See the
745
+ [data visualization guide](https://github.com/santi020k/lumen/blob/main/docs/data-visualization.md#calendars-funnels-and-box-plots).
746
+
747
+ Media controllers for ImageComparison and FileUpload load only when matching components are present.
748
+ Keep mounting `UIPrimitives` once; no additional consumer setup is required. Image comparisons
749
+ follow their current native form owner for reset and honor canceled resets. Reinitialize after
750
+ document adoption to register destination reset delegation without duplicate input listeners.
751
+
752
+ ## Visual interactions and product blocks
753
+
754
+ See [visual interactions](https://github.com/santi020k/lumen/blob/main/docs/visual-interactions.md) for keyed motion, semantic effects,
755
+ chart continuity, AI surfaces, optional SDK integrations, and the four installable product recipes.
756
+ Import `@santi020k/lumen/styles/motion.css` alongside the base stylesheet to opt into native
757
+ disclosure height transitions and the CSS reduction scope. This small optional stylesheet works
758
+ with Astro, React, and Elements and keeps those effects out of the default stylesheet. The presence
759
+ helper's system and local reduced-motion checks work without this CSS import.
760
+
761
+ ## Device demonstrations
762
+
763
+ `DeviceFrame` presents slotted HTML, an image, or a titled iframe inside `macbook-pro`, `macbook-air`, `imac`,
764
+ `iphone`, `pixel`, and `ipad-pro` frames, with generic `laptop`, `desktop`, `android`, and `tablet` options.
765
+ Device-specific enclosures include a MacBook Pro display notch and full front edge, a tapered MacBook Air deck, curved iMac
766
+ stand and chin, rounded phone glass with separate rails and buttons, and an iPad Pro with even
767
+ bezels and no Home button. The generic `tablet` retains its classic enclosure.
768
+ Shells are decorative and do not emulate device hardware. `orientation` selects portrait or landscape; `tone` selects
769
+ light or dark chrome independently of the screen content. Use `color="white"`,
770
+ `color="black"`, or a 3-, 4-, 6-, or 8-digit hex color such as `color="#a9b8ac"` for
771
+ a custom hardware finish. `color` takes precedence over `tone`; invalid values fall back to the
772
+ tone. The finish stays independent of the content theme. For CSS-driven updates, set
773
+ `--ui-device-color` to a CSS color on the frame.
774
+
775
+ Iframe layouts use the preset screen viewport and scale to the available width. Override
776
+ `screenWidth` and `screenHeight` for a custom viewport (1–16384 CSS pixels). Slotted HTML shares
777
+ the host viewport; container queries can adapt it to the screen. Images preserve their proportions.
778
+ `scroll={false}` clips HTML overflow; an iframe manages its own scrolling.
779
+
780
+ Supply image alternative text and iframe titles. Consumers own iframe `sandbox`, `allow`,
781
+ loading, and referrer policies. Remote sites can refuse embedding through their response headers.
782
+ The decorative shell does not alter focus or intercept interactions. Camera and home-indicator
783
+ details occupy separate chrome outside live HTML and iframe viewports. Direct phone images extend
784
+ under the decorative camera and home indicator for a full-bleed presentation.
785
+ Screen presets are 1280 × 800 for MacBook Pro and MacBook Air, 1440 × 810 for iMac, 390 × 844 for iPhone,
786
+ 412 × 915 for Pixel, and 834 × 1194 for iPad Pro (1194 × 834 in landscape). These are demonstration
787
+ viewports, not physical display specifications.
788
+
789
+ ```astro
790
+ ---
791
+ import { DeviceFrame } from '@santi020k/lumen-astro'
792
+ ---
793
+ <DeviceFrame device="iphone">
794
+ <iframe src="/demo" title="Mobile application demo" loading="lazy"></iframe>
795
+ </DeviceFrame>
796
+ ```
797
+
798
+ The component bundles its resize behavior; `UIPrimitives` is not required for this frame.
799
+
800
+ ## World map
801
+
802
+ WorldMap supports highlighted countries, location markers, dotted or solid styles, country selection,
803
+ and theme customization. Use `initialView="highlighted"` (Elements: `initial-view`) to start with a
804
+ regional view. Zoom toward the cursor with Ctrl/Cmd-scroll, or fit highlighted countries using the
805
+ map controls. Adopted maps emit selection events in their current document; reinitialization follows
806
+ interaction mode changes without duplicating listeners or resetting zoom. Import geography explicitly from
807
+ `@santi020k/lumen-core/world-map-data`; it is excluded from root exports. See the
808
+ [WorldMap usage guide](https://github.com/santi020k/lumen/blob/main/docs/world-map.md) for adapter examples, events, localization,
809
+ accessibility, and customization.
810
+
811
+ ## Studio media workspace
812
+
813
+ Compose MediaViewport, MediaThumbnail, MediaFilmstrip and ImageComparison modes with the
814
+ [Studio media workspace recipes](https://github.com/santi020k/lumen/blob/main/docs/studio-media-workspaces.md). Applications retain
815
+ media loading, selection, adjustment algorithms, processing, export and persistence.
816
+
817
+ ## Resources
818
+
819
+ | Guide | What you will find |
820
+ | --- | --- |
821
+ | [Styling contract](https://github.com/santi020k/lumen/blob/main/docs/styling-contract.md) | Stable styling parts, component variables, and overrides. |
822
+ | [Error handling](https://github.com/santi020k/lumen/blob/main/docs/error-handling.md) | Field feedback, summaries, alerts, and recovery surfaces. |
823
+ | [Contributing](https://github.com/santi020k/lumen/blob/main/CONTRIBUTING.md) | Setup, checks, and contribution workflow. |
824
+ | [Release history](https://github.com/santi020k/lumen/releases) | Published releases and version notes. |
825
+
826
+ Part of [Lumen UI](https://lumen.santi020k.com), created by [Santiago Molina](https://santi020k.com).
827
+ Licensed under [MIT](https://github.com/santi020k/lumen/blob/main/LICENSE); third-party artwork retains its own notices.
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { formatLumenAmountDraft, getLumenAmountValue, type LumenAmountOptions, resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends Omit<HTMLAttributes<'input'>, 'value' | 'type' | 'min' | 'max' | 'step'>, LumenAmountOptions {
7
+ value?: string
8
+ defaultValue?: string
9
+ invalidMessage?: string
10
+ className?: string
11
+ }
12
+
13
+ const { value, defaultValue, locale = 'en-US', fractionDigits = 2, allowNegative = false, invalidMessage, name, form, disabled, class: classProp = '', className = '', ...rest } = Astro.props
14
+ const initial = value ?? defaultValue ?? ''
15
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-input', 'ui-amount-field'], classProp, className)
16
+ ---
17
+
18
+ <span data-ui-amount-field locale={locale} fraction-digits={fractionDigits} allow-negative={allowNegative ? '' : undefined} value={value} default-value={defaultValue ?? initial} invalid-message={invalidMessage}>
19
+ <input {...passthrough} class:list={classList} data-ui-amount-input type="text" inputmode="decimal" value={formatLumenAmountDraft(initial, { locale, fractionDigits, allowNegative })} form={form} disabled={disabled} />
20
+ <input data-ui-amount-value type="hidden" name={name} form={form} disabled={disabled} value={getLumenAmountValue(initial) ?? ''} />
21
+ </span>
@@ -28,7 +28,7 @@ const {
28
28
  ...rest
29
29
  } = Astro.props
30
30
 
31
- const safeDecimals = Math.max(0, Math.min(20, Math.floor(decimals)))
31
+ const safeDecimals = Math.max(0, Math.min(20, (Math.floor(decimals) || 0)))
32
32
 
33
33
  const formatter = new Intl.NumberFormat(locale, {
34
34
  maximumFractionDigits: safeDecimals,
@@ -0,0 +1,30 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import type { LumenApprovalStatus } from '@santi020k/lumen-core'
5
+
6
+ import Button from './Button.astro'
7
+ import Card from './Card.astro'
8
+ import Stack from './Stack.astro'
9
+
10
+ interface Props extends HTMLAttributes<'div'> {
11
+ approveLabel?: string
12
+ disabled?: boolean
13
+ label: string
14
+ rejectLabel?: string
15
+ requestId: string
16
+ status?: LumenApprovalStatus
17
+ statusLabel: string
18
+ }
19
+
20
+ const { approveLabel = 'Approve', class: className, disabled = false, label, rejectLabel = 'Reject', requestId, status = 'pending', statusLabel, ...rest } = Astro.props
21
+ ---
22
+
23
+ <Card {...rest} aria-label={label} class:list={['ui-approval-card', className]} data-disabled={String(disabled)} data-ui-approval-card data-request-id={requestId} data-status={status}>
24
+ <Stack gap="group"><strong>{label}</strong><slot /><span role="status">{statusLabel}</span>
25
+ <Stack direction="horizontal" gap="related" wrap>
26
+ <Button data-ui-approval-response="approve" disabled={disabled || status !== 'pending'} type="button">{approveLabel}</Button>
27
+ <Button data-ui-approval-response="reject" disabled={disabled || status !== 'pending'} type="button" variant="secondary">{rejectLabel}</Button>
28
+ </Stack>
29
+ </Stack>
30
+ </Card>
@@ -0,0 +1,15 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'ul'> {
7
+ class?: string | null
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', ...rest } = Astro.props
12
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-attachment-list'], classProp, className)
13
+ ---
14
+
15
+ <ul class:list={classList} data-slot="attachment-list" {...passthrough}><slot /></ul>
@@ -0,0 +1,51 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import {
5
+ type LumenAttachmentPreviewLabels,
6
+ lumenAttachmentPreviewLabels,
7
+ type LumenAttachmentPreviewState,
8
+ resolveLumenAstroProps,
9
+ resolveLumenAttachmentPreviewState
10
+ } from '@santi020k/lumen-core'
11
+
12
+ interface Props extends HTMLAttributes<'figure'> {
13
+ alt: string
14
+ class?: string | null
15
+ className?: string
16
+ contentType?: string
17
+ labels?: Partial<LumenAttachmentPreviewLabels>
18
+ retryKey?: number | string
19
+ src?: string
20
+ state?: LumenAttachmentPreviewState
21
+ }
22
+
23
+ const { alt, class: classProp = '', className = '', contentType, labels, retryKey, src, state: requested, ...rest } = Astro.props
24
+ const state = resolveLumenAttachmentPreviewState(src, contentType, requested)
25
+ const canPreview = resolveLumenAttachmentPreviewState(src, contentType) === 'ready'
26
+ const copy = { ...lumenAttachmentPreviewLabels, ...labels }
27
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-attachment-preview'], classProp, className)
28
+ ---
29
+
30
+ <figure
31
+ class:list={classList}
32
+ {...passthrough}
33
+ aria-busy={state === 'loading'}
34
+ data-state={state}
35
+ data-ui-attachment-preview
36
+ data-ui-attachment-preview-state={requested}
37
+ data-ui-attachment-preview-content-type={contentType}
38
+ data-ui-attachment-preview-retry-key={retryKey}
39
+ data-ui-attachment-preview-error-label={copy.error}
40
+ data-ui-attachment-preview-loading-label={copy.loading}
41
+ data-ui-attachment-preview-unavailable-label={copy.unavailable}
42
+ >
43
+ <div data-slot="attachment-preview-media" hidden={state !== 'ready'}>
44
+ {/* Browser-owned private URLs and object URLs must not enter Astro's server image optimizer. */}
45
+ {canPreview && <img src={src} alt={alt} data-ui-attachment-preview-image loading="eager" />}
46
+ </div>
47
+ <p role="status" data-slot="attachment-preview-fallback" data-ui-attachment-preview-message hidden={state === 'ready'}>{state === 'ready' ? '' : copy[state]}</p>
48
+ {Astro.slots.has('caption') && <figcaption><slot name="caption" /></figcaption>}
49
+ <slot />
50
+ {Astro.slots.has('actions') && <div data-slot="attachment-preview-actions"><slot name="actions" /></div>}
51
+ </figure>
@@ -30,6 +30,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
30
30
 
31
31
  <span
32
32
  class:list={classList}
33
+ role={!src && alt ? 'img' : undefined}
34
+ aria-label={!src && alt ? alt : undefined}
33
35
  {...passthrough}
34
36
  >
35
37
  {src ? <img alt={alt} src={src} /> : fallbackText && <span aria-hidden={fallback ? undefined : 'true'}>{fallbackText}</span>}