@santi020k/lumen-astro 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 (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 +122 -66
  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
@@ -0,0 +1,18 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'div'> {
7
+ class?: string | null
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', ...rest } = Astro.props
12
+
13
+ const { classList, passthrough } = resolveLumenAstroProps(
14
+ rest, ['ui-description-item', 'ui-descriptions__item'], classProp, className
15
+ )
16
+ ---
17
+
18
+ <div class:list={classList} data-slot="description-item" {...passthrough}><slot /></div>
@@ -0,0 +1,18 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'dt'> {
7
+ class?: string | null
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', ...rest } = Astro.props
12
+
13
+ const { classList, passthrough } = resolveLumenAstroProps(
14
+ rest, ['ui-description-term', 'ui-descriptions__term'], classProp, className
15
+ )
16
+ ---
17
+
18
+ <dt class:list={classList} data-slot="description-term" {...passthrough}><slot /></dt>
@@ -0,0 +1,79 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import {
5
+ type LumenDeviceFrameColor, type LumenDeviceFrameDevice, type LumenDeviceFrameOrientation, type LumenDeviceFrameTone,
6
+ resolveLumenAstroProps, resolveLumenDeviceFrame, resolveLumenDeviceFrameColor
7
+ } from '@santi020k/lumen-core'
8
+
9
+ interface Props extends HTMLAttributes<'div'> {
10
+ device?: LumenDeviceFrameDevice
11
+ orientation?: LumenDeviceFrameOrientation
12
+ tone?: LumenDeviceFrameTone
13
+ color?: LumenDeviceFrameColor
14
+ screenWidth?: number
15
+ screenHeight?: number
16
+ scroll?: boolean
17
+ className?: string
18
+ }
19
+
20
+ const { device = 'laptop', orientation, tone = 'dark', color, screenWidth, screenHeight, scroll = true,
21
+ class: classProp, className, style: consumerStyle, ...rest } = Astro.props
22
+
23
+ const size = resolveLumenDeviceFrame(device, orientation, screenWidth, screenHeight)
24
+ const resolvedColor = resolveLumenDeviceFrameColor(color)
25
+
26
+ const frameStyle = typeof consumerStyle === 'string' ?
27
+ `${consumerStyle}${resolvedColor ? `;--ui-device-color:${resolvedColor}` : ''}` :
28
+ { ...consumerStyle, ...(resolvedColor ? { '--ui-device-color': resolvedColor } : {}) }
29
+
30
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-device-frame'], classProp, className)
31
+ ---
32
+
33
+ <div class:list={classList} data-device={device} data-tone={tone} data-orientation={orientation} style={frameStyle} {...passthrough}>
34
+ <div class="ui-device-frame__shell">
35
+ <span class="ui-device-frame__hardware" aria-hidden="true">
36
+ <span class="ui-device-frame__action" />
37
+ <span class="ui-device-frame__volume-up" />
38
+ <span class="ui-device-frame__volume-down" />
39
+ <span class="ui-device-frame__power" />
40
+ <span class="ui-device-frame__rail-left" />
41
+ <span class="ui-device-frame__rail-right" />
42
+ </span>
43
+ <div class="ui-device-frame__glass">
44
+ <span aria-hidden="true" class="ui-device-frame__camera"></span>
45
+ <lumen-device-screen
46
+ class="ui-device-frame__screen"
47
+ data-scroll={String(scroll)}
48
+ data-ui-device-screen
49
+ data-screen-width={size.width}
50
+ style={`--ui-device-frame-width: ${size.width}px; --ui-device-frame-height: ${size.height}px; aspect-ratio: ${size.width} / ${size.height}`}
51
+ >
52
+ <slot />
53
+ </lumen-device-screen>
54
+ </div>
55
+ </div>
56
+ <span aria-hidden="true" class="ui-device-frame__base"></span>
57
+ </div>
58
+
59
+ <script>
60
+ import { observeLumenDeviceFrame } from '@santi020k/lumen-core'
61
+
62
+ class LumenDeviceScreen extends HTMLElement {
63
+ private cleanup: (() => void) | undefined
64
+
65
+ connectedCallback(): void {
66
+ this.cleanup?.()
67
+
68
+ this.cleanup = observeLumenDeviceFrame(this, Number(this.dataset.screenWidth))
69
+ }
70
+
71
+ disconnectedCallback(): void {
72
+ this.cleanup?.()
73
+
74
+ this.cleanup = undefined
75
+ }
76
+ }
77
+
78
+ if (!customElements.get('lumen-device-screen')) customElements.define('lumen-device-screen', LumenDeviceScreen)
79
+ </script>
@@ -0,0 +1,20 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'div'> {
7
+ class?: string | null
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', ...rest } = Astro.props
12
+
13
+ const { classList, passthrough } = resolveLumenAstroProps(
14
+ rest, ['ui-dialog-body'], classProp, className
15
+ )
16
+ ---
17
+
18
+ <div class:list={classList} data-slot="dialog-body" {...passthrough}>
19
+ <slot />
20
+ </div>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { composeClassName } from '@santi020k/lumen-core'
5
+
6
+ import Button from './Button.astro'
7
+
8
+ interface Props extends HTMLAttributes<'button'> {
9
+ class?: string | null
10
+ className?: string
11
+ loading?: boolean
12
+ size?: 'default' | 'sm' | 'lg' | 'icon'
13
+ variant?: 'default' | 'destructive' | 'ghost' | 'link' | 'outline' | 'secondary'
14
+ }
15
+
16
+ const { class: classProp = '', className = '', ...rest } = Astro.props
17
+ ---
18
+
19
+ <Button {...rest} class={composeClassName('ui-dialog-close', classProp, className)} data-slot="dialog-close" data-ui-dialog-close>
20
+ <slot />
21
+ </Button>
@@ -0,0 +1,20 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'footer'> {
7
+ class?: string | null
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', ...rest } = Astro.props
12
+
13
+ const { classList, passthrough } = resolveLumenAstroProps(
14
+ rest, ['ui-dialog-footer'], classProp, className
15
+ )
16
+ ---
17
+
18
+ <footer class:list={classList} data-slot="dialog-footer" {...passthrough}>
19
+ <slot />
20
+ </footer>
@@ -0,0 +1,20 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'header'> {
7
+ class?: string | null
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', ...rest } = Astro.props
12
+
13
+ const { classList, passthrough } = resolveLumenAstroProps(
14
+ rest, ['ui-dialog-header'], classProp, className
15
+ )
16
+ ---
17
+
18
+ <header class:list={classList} data-slot="dialog-header" {...passthrough}>
19
+ <slot />
20
+ </header>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'h2'> {
7
+ as?: 'h2' | 'h3' | 'h4'
8
+ class?: string | null
9
+ className?: string
10
+ }
11
+
12
+ const { as: Tag = 'h2', class: classProp = '', className = '', ...rest } = Astro.props
13
+
14
+ const { classList, passthrough } = resolveLumenAstroProps(
15
+ rest, ['ui-dialog-title'], classProp, className
16
+ )
17
+ ---
18
+
19
+ <Tag class:list={classList} data-slot="dialog-title" {...passthrough}>
20
+ <slot />
21
+ </Tag>
@@ -0,0 +1,8 @@
1
+ ---
2
+ import type { ComponentProps } from 'astro/types'
3
+
4
+ import ComparisonChart from '../internal/ComparisonChart.astro'
5
+
6
+ type Props = Omit<ComponentProps<typeof ComparisonChart>, 'paired'>
7
+ ---
8
+ <ComparisonChart {...Astro.props} class:list={['ui-dumbbell-chart', Astro.props.class, Astro.props.className]} paired={true} />
@@ -8,6 +8,7 @@ interface Props extends Omit<HTMLAttributes<'input'>, 'type' | 'size'> {
8
8
  className?: string
9
9
  label?: string
10
10
  hint?: string
11
+ selectedFilesLabel?: string
11
12
  }
12
13
 
13
14
  const {
@@ -15,6 +16,7 @@ const {
15
16
  className = '',
16
17
  label = 'Choose a file or drag it here',
17
18
  hint,
19
+ selectedFilesLabel = '{count} files selected',
18
20
  id,
19
21
  ...rest
20
22
  } = Astro.props
@@ -24,7 +26,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
24
26
  ], classProp, className)
25
27
  ---
26
28
 
27
- <label class:list={classList} data-ui-file-upload for={id}>
29
+ <label class:list={classList} data-ui-file-upload data-ui-selected-files-label={selectedFilesLabel} for={id}>
28
30
  <input class="ui-file-upload__input" data-ui-file-upload-input id={id} type="file" {...passthrough} />
29
31
  <svg
30
32
  aria-hidden="true"
@@ -0,0 +1,25 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
5
+
6
+ interface Props extends HTMLAttributes<'section'> {
7
+ label?: string
8
+ resultLabel?: string
9
+ pending?: boolean
10
+ open?: boolean
11
+ class?: string | null
12
+ className?: string
13
+ }
14
+
15
+ const { label = 'Filters', resultLabel, pending = false, open = true, class: classProp, className, ...rest } = Astro.props
16
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-filter-bar'], classProp, className)
17
+ ---
18
+ <section class:list={classList} aria-label={label} aria-busy={pending} {...passthrough}>
19
+ <details open={open}>
20
+ <summary>{label}</summary>
21
+ <div class="ui-filter-bar__controls"><slot /></div>
22
+ </details>
23
+ <div class="ui-filter-bar__active"><slot name="active" /><slot name="actions" /></div>
24
+ <p class="ui-filter-bar__results" role="status" aria-live="polite" aria-atomic="true">{resultLabel}</p>
25
+ </section>
@@ -0,0 +1,50 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { createLumenFunnelGeometry, getLumenChartToneClassName, type LumenChartLabels, type LumenFunnelDatum, resolveLumenChartLabels } from '@santi020k/lumen-core'
5
+
6
+ import Chart from './Chart.astro'
7
+
8
+ interface Props extends HTMLAttributes<'figure'> {
9
+ data: readonly LumenFunnelDatum[]
10
+ className?: string
11
+ heading?: string
12
+ description?: string
13
+ caption?: string
14
+ presentation?: 'bare' | 'default'
15
+ summary?: string
16
+ formatValue?: (value: number) => string
17
+ labels?: Partial<LumenChartLabels>
18
+ showTable?: boolean
19
+ }
20
+
21
+ const { data, formatValue = String, labels, showTable = true, summary, class: classProp, className, ...props } = Astro.props
22
+ const text = resolveLumenChartLabels(labels)
23
+ const model = createLumenFunnelGeometry(data)
24
+ const format = (value: number | null) => value === null ? text.notAvailable : formatValue(value)
25
+ const fallback = model.valid ? text.empty : text.invalidData
26
+ const factual = !model.valid || model.rows.length === 0 ? fallback : model.rows.map(row => `${row.label}: ${format(row.value)}`).join('. ')
27
+ ---
28
+ <Chart {...props} class:list={['ui-funnel-chart', classProp, className]} summary={summary ?? factual}>
29
+ {!model.valid || model.rows.length === 0 ?
30
+ <p class="ui-chart__empty" role="status">{model.valid ? text.empty : text.invalidData}</p> :
31
+ (
32
+ <>
33
+ <ol class="ui-funnel-chart__rows">{model.rows.map((row, index) => (
34
+ <li class={getLumenChartToneClassName(row.tone, index)}><div class="
35
+ ui-funnel-chart__label
36
+ "
37
+ ><span>{row.label}</span><strong>{format(row.value)}</strong></div><div
38
+ class="ui-funnel-chart__track"
39
+ aria-hidden="true"
40
+ >{row.ratio !== null && (
41
+ <span
42
+ class="ui-funnel-chart__bar"
43
+ style={{ width: `${row.ratio * 100}%` }}
44
+ />
45
+ )}</div></li>
46
+ ))}</ol>
47
+ {showTable && <details class="ui-chart__data"><summary>{text.viewData}</summary><div role="group" tabindex="0" aria-label={text.chartData}><table><thead><tr><th scope="col">{text.category}</th><th scope="col">{text.value}</th></tr></thead><tbody>{model.rows.map(row => <tr><th scope="row">{row.label}</th><td>{format(row.value)}</td></tr>)}</tbody></table></div></details>}
48
+ </>
49
+ )}
50
+ </Chart>
@@ -8,7 +8,7 @@ interface Props extends HTMLAttributes<'div'> {
8
8
  class?: string | null
9
9
  className?: string
10
10
  columns?: 1 | 2 | 3 | 4 | 6 | 12 | 'auto'
11
- gap?: 'lg' | 'md' | 'none' | 'sm' | 'xl'
11
+ gap?: '2xl' | '3xl' | 'group' | 'lg' | 'md' | 'none' | 'related' | 'section' | 'sm' | 'xl' | 'xs'
12
12
  minItemWidth?: string
13
13
  }
14
14
 
@@ -17,7 +17,7 @@ const {
17
17
  class: classProp = '',
18
18
  className = '',
19
19
  columns = 'auto',
20
- gap = 'md',
20
+ gap = 'group',
21
21
  minItemWidth,
22
22
  style,
23
23
  ...rest
@@ -1,16 +1,21 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types'
3
3
 
4
- import { createLumenHeatmapGeometry, type LumenChartLabels, type LumenHeatmapDatum, resolveLumenChartLabels } from '@santi020k/lumen-core'
4
+ import { createLumenHeatmapDatumActivation, createLumenHeatmapModel, getLumenHeatmapColor, type LumenChartLabels, type LumenHeatmapDatum, type LumenHeatmapOptions, resolveLumenChartLabels } from '@santi020k/lumen-core'
5
+
6
+ import { createChartDatumAction } from '../runtime/chart-actions'
5
7
 
6
8
  import Chart from './Chart.astro'
9
+ import ChartDatumActions from './ChartDatumActions.astro'
7
10
 
8
- interface Props extends HTMLAttributes<'figure'> {
11
+ interface Props extends HTMLAttributes<'figure'>, LumenHeatmapOptions {
12
+ showLegend?: boolean
9
13
  caption?: string
10
14
  class?: string | null
11
15
  className?: string
12
16
  data: readonly LumenHeatmapDatum[]
13
17
  description?: string
18
+ drilldown?: boolean
14
19
  formatValue?: (value: number) => string
15
20
  heading?: string
16
21
  labels?: Partial<LumenChartLabels>
@@ -18,18 +23,62 @@ interface Props extends HTMLAttributes<'figure'> {
18
23
  summary?: string
19
24
  }
20
25
 
21
- const { caption, class: classProp = '', className = '', data, description, formatValue = String, heading, labels, showTable = true, summary: summaryProp, ...rest } = Astro.props
22
- const geometry = createLumenHeatmapGeometry(data)
23
- const hasData = geometry.cells.some(cell => cell.value !== null && Number.isFinite(cell.value))
26
+ const { colorScale = 'sequential', midpoint = 0, domain, showLegend = true, caption, class: classProp = '', className = '', data, description, drilldown = false, formatValue = String, heading, labels, showTable = true, summary: summaryProp, ...rest } = Astro.props
27
+ const geometry = createLumenHeatmapModel(data, { colorScale, midpoint, ...(domain ? { domain } : {}) })
28
+ const availableCells = geometry.cells.filter(cell => cell.value !== null && Number.isFinite(cell.value))
29
+ const hasData = availableCells.length > 0
24
30
  const resolvedLabels = resolveLumenChartLabels(labels)
25
- const summary = summaryProp ?? resolvedLabels.formatHeatmapSummary(geometry.cells.filter(cell => cell.value !== null && Number.isFinite(cell.value)).length)
31
+ const summary = summaryProp ?? resolvedLabels.formatHeatmapSummary(availableCells.length)
32
+
33
+ const datumActions = drilldown ?
34
+ availableCells.map(cell => createChartDatumAction(
35
+ createLumenHeatmapDatumActivation(cell),
36
+ `${cell.xLabel ?? cell.x} · ${cell.yLabel ?? cell.y}: ${cell.label ?? formatValue(cell.value ?? 0)}`,
37
+ resolvedLabels
38
+ )) :
39
+ []
40
+
41
+ const datumActionByCell = new Map(availableCells.map((cell, index) => [cell, datumActions[index]]))
42
+
26
43
  ---
27
44
 
28
- <Chart class:list={['ui-heatmap', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
45
+ <Chart class:list={['ui-heatmap', classProp, className]} {caption} {description} {heading} {summary} {...rest} data-ui-chart-activation={drilldown || undefined}>
29
46
  {!hasData && <p class="ui-chart__empty" role="status">{resolvedLabels.empty}</p>}
30
- <div class="ui-chart__plot" hidden={!hasData}><svg aria-hidden="true" viewBox={`0 0 ${geometry.width} ${geometry.height}`}><g class="
31
- ui-heatmap__cells
32
- "
33
- >{geometry.cells.filter(cell => cell.value !== null && Number.isFinite(cell.value)).map(cell => <rect height={Math.max(0, cell.height - 2)} opacity={Math.max(0.12, cell.ratio)} width={Math.max(0, cell.width - 2)} x={cell.xCoordinate + 1} y={cell.yCoordinate + 1}><title>{`${cell.xLabel ?? cell.x} · ${cell.yLabel ?? cell.y}: ${cell.label ?? formatValue(cell.value ?? 0)}`}</title></rect>)}</g></svg></div>
34
- {showTable && <details class="ui-chart__data"><summary>{resolvedLabels.viewData}</summary><div><table><thead><tr><th scope="col">{resolvedLabels.column}</th><th scope="col">{resolvedLabels.row}</th><th scope="col">{resolvedLabels.value}</th></tr></thead><tbody>{data.map(cell => <tr><th scope="row">{cell.xLabel ?? cell.x}</th><td>{cell.yLabel ?? cell.y}</td><td>{cell.label ?? (cell.value === null || !Number.isFinite(cell.value) ? resolvedLabels.notAvailable : formatValue(cell.value))}</td></tr>)}</tbody></table></div></details>}
47
+ <div
48
+ aria-label={rest['aria-label'] ?? heading ?? resolvedLabels.chartData}
49
+ class="ui-chart__plot"
50
+ role="region"
51
+ tabindex="0"
52
+ hidden={geometry.cells.length === 0}
53
+ >
54
+ <svg aria-hidden="true" viewBox={`0 0 ${geometry.width} ${geometry.height}`}>
55
+ <g class="ui-chart__axis-labels">
56
+ {geometry.xTicks.map(tick => <text text-anchor={tick.textAnchor} x={tick.position} y="298">{tick.label}</text>)}
57
+ {geometry.yTicks.map(tick => <text class="ui-heatmap__row-label" text-anchor="end" dominant-baseline="middle" x="108" y={tick.position}>{tick.label}</text>)}
58
+ </g>
59
+ <g class="ui-heatmap__cells">{geometry.cells.map(cell => {
60
+ const missing = cell.value === null || !Number.isFinite(cell.value)
61
+
62
+ return (
63
+ <g>
64
+ <rect data-ui-chart-datum={datumActionByCell.get(cell)?.serialized} height={Math.max(0, cell.height - 2)} width={Math.max(0, cell.width - 2)} x={cell.xCoordinate + 1} y={cell.yCoordinate + 1} style={{ fill: getLumenHeatmapColor(cell.value, geometry.domain, colorScale, geometry.midpoint) }}>
65
+ <title>{`${cell.xLabel ?? cell.x} · ${cell.yLabel ?? cell.y}: ${missing ? resolvedLabels.notAvailable : cell.label ?? formatValue(cell.value ?? 0)}`}</title>
66
+ </rect>
67
+ {missing && <text class="ui-heatmap__missing" text-anchor="middle" dominant-baseline="middle" x={cell.xCoordinate + cell.width / 2} y={cell.yCoordinate + cell.height / 2}>×</text>}
68
+ </g>
69
+ )
70
+ })}</g>
71
+ </svg>
72
+ </div>
73
+ {showLegend && geometry.cells.length > 0 && (
74
+ <div class="ui-heatmap__legend" aria-label={resolvedLabels.chartLegend}>
75
+ <span>{formatValue(geometry.domain.min)}</span><span
76
+ style={{ background: geometry.legendBackground }}
77
+ class="ui-heatmap__scale"
78
+ >{colorScale === 'diverging' && <span style={{ left: `${geometry.midpointPercent}%` }}>{formatValue(geometry.midpoint)}</span>}</span><span>{formatValue(geometry.domain.max)}</span>
79
+ <span>× {resolvedLabels.notAvailable}</span>
80
+ </div>
81
+ )}
82
+ {showTable && <details class="ui-chart__data"><summary>{resolvedLabels.viewData}</summary><div role="group" tabindex="0" aria-label={resolvedLabels.chartData}><table><thead><tr><th scope="col">{resolvedLabels.column}</th><th scope="col">{resolvedLabels.row}</th><th scope="col">{resolvedLabels.value}</th></tr></thead><tbody>{geometry.cells.map(cell => <tr><th scope="row">{cell.xLabel ?? cell.x}</th><td>{cell.yLabel ?? cell.y}</td><td>{cell.label ?? (cell.value === null || !Number.isFinite(cell.value) ? resolvedLabels.notAvailable : formatValue(cell.value))}</td></tr>)}</tbody></table></div></details>}
83
+ {drilldown && <ChartDatumActions actions={datumActions} label={resolvedLabels.exploreData} />}
35
84
  </Chart>
@@ -0,0 +1,30 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import { createLumenHistogramGeometry, type LumenChartLabels, type LumenChartTone, type LumenHistogramBin, resolveLumenChartLabels } from '@santi020k/lumen-core'
5
+
6
+ import IntervalChart from '../internal/IntervalChart.astro'
7
+
8
+ interface Props extends HTMLAttributes<'figure'> {
9
+ bins: readonly LumenHistogramBin[]
10
+ caption?: string
11
+ class?: string | null
12
+ className?: string
13
+ description?: string
14
+ formatBoundary?: (value: number) => string
15
+ formatValue?: (value: number) => string
16
+ frequency?: 'count' | 'density'
17
+ heading?: string
18
+ labels?: Partial<LumenChartLabels>
19
+ presentation?: 'bare' | 'default'
20
+ showTable?: boolean
21
+ summary?: string
22
+ tone?: LumenChartTone
23
+ valueLabel?: string
24
+ }
25
+
26
+ const { bins, class: classProp, className, formatBoundary = String, formatValue = String, frequency = 'count', labels, tone = 'series-1', valueLabel = resolveLumenChartLabels(labels)[frequency === 'density' ? 'density' : 'count'], ...rest } = Astro.props
27
+ const model = createLumenHistogramGeometry(bins, { formatBoundary, formatValue, frequency, tone })
28
+ ---
29
+
30
+ <IntervalChart {...rest} class:list={['ui-histogram', classProp, className]} {model} {labels} {formatBoundary} {formatValue} {valueLabel} />
@@ -18,16 +18,20 @@ interface Props extends HTMLAttributes<'span'> {
18
18
  }
19
19
 
20
20
  const {
21
+ 'aria-hidden': ariaHidden,
22
+ 'aria-label': ariaLabel,
21
23
  class: classProp = '',
22
24
  className = '',
23
25
  decorative,
24
26
  label,
25
27
  name,
28
+ role,
26
29
  size = 'default',
27
30
  ...rest
28
31
  } = Astro.props
29
32
 
30
- const isDecorative = decorative ?? !label
33
+ const accessibleLabel = label ?? ariaLabel
34
+ const isDecorative = decorative ?? (ariaHidden === undefined ? !accessibleLabel : ariaHidden === true || ariaHidden === 'true')
31
35
  const iconSvg = name ? renderLumenIconSvg(name) : ''
32
36
 
33
37
  if (name && !iconSvg && import.meta.env.DEV) {
@@ -47,9 +51,9 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
47
51
 
48
52
  <span
49
53
  aria-hidden={isDecorative ? 'true' : undefined}
50
- aria-label={!isDecorative ? label : undefined}
54
+ aria-label={!isDecorative ? accessibleLabel : undefined}
51
55
  class:list={classList}
52
- role={!isDecorative ? 'img' : undefined}
56
+ role={!isDecorative ? role ?? 'img' : undefined}
53
57
  {...passthrough}
54
58
  >
55
59
  {name ? <Fragment set:html={iconSvg} /> : <slot />}
@@ -0,0 +1,74 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ import {
5
+ formatLumenImageComparisonValue,
6
+ type LumenImageComparisonMode,
7
+ normalizeLumenImageComparisonMode,
8
+ normalizeLumenImageComparisonRatio,
9
+ normalizeLumenImageComparisonValue,
10
+ resolveLumenAstroProps
11
+ } from '@santi020k/lumen-core'
12
+
13
+ interface Props extends HTMLAttributes<'figure'> {
14
+ afterLabel?: string
15
+ beforeLabel?: string
16
+ class?: string | null
17
+ className?: string
18
+ disabled?: boolean
19
+ fit?: 'contain' | 'cover'
20
+ label: string
21
+ locale?: string
22
+ mode?: LumenImageComparisonMode
23
+ name?: string
24
+ ratio?: number
25
+ value?: number
26
+ }
27
+
28
+ const {
29
+ afterLabel = 'After', beforeLabel = 'Before', class: classProp = '', className = '',
30
+ disabled = false, fit = 'cover', label, locale, mode: requestedMode = 'reveal', name, ratio = 16 / 9, value = 50,
31
+ ...rest
32
+ } = Astro.props
33
+
34
+ const mode = normalizeLumenImageComparisonMode(requestedMode)
35
+ const position = normalizeLumenImageComparisonValue(value)
36
+ const aspectRatio = normalizeLumenImageComparisonRatio(ratio)
37
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-image-comparison'], classProp, className)
38
+ ---
39
+
40
+ <figure
41
+ class:list={classList}
42
+ data-ui-image-comparison
43
+ data-after-label={afterLabel}
44
+ data-disabled={disabled ? 'true' : undefined}
45
+ data-fit={fit}
46
+ data-mode={mode}
47
+ data-locale={locale}
48
+ {...passthrough}
49
+ >
50
+ <div class="ui-image-comparison__frame" style={`--ui-image-comparison-position: ${position}%; --ui-image-comparison-ratio: ${aspectRatio}`}>
51
+ <div class="ui-image-comparison__before" hidden={mode === 'after'}><slot name="before" /></div>
52
+ <div class="ui-image-comparison__after" hidden={mode === 'before'}><slot name="after" /></div>
53
+ <div aria-hidden="true" class="ui-image-comparison__divider"></div>
54
+ </div>
55
+ <div aria-hidden="true" class="ui-image-comparison__labels"><span hidden={mode === 'after'}>{beforeLabel}</span><span hidden={mode === 'before'}>{afterLabel}</span></div>
56
+ <figcaption class="ui-image-comparison__caption">
57
+ <label class="ui-image-comparison__control" hidden={mode !== 'reveal'}>
58
+ <span>{label}</span>
59
+ <input
60
+ aria-valuetext={formatLumenImageComparisonValue(position, afterLabel, locale)}
61
+ class="ui-slider ui-image-comparison__range"
62
+ data-ui-image-comparison-input
63
+ disabled
64
+ max="100"
65
+ min="0"
66
+ name={name}
67
+ step="1"
68
+ type="range"
69
+ value={position}
70
+ />
71
+ </label>
72
+ <slot />
73
+ </figcaption>
74
+ </figure>
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types'
3
3
 
4
- import { resolveLumenAstroProps } from '@santi020k/lumen-core'
4
+ import { type LumenControlVisualSize, resolveLumenAstroProps } from '@santi020k/lumen-core'
5
5
 
6
6
  interface Props extends Omit<HTMLAttributes<'input'>, 'size'> {
7
7
  class?: string | null
8
8
  className?: string
9
9
  size?: HTMLAttributes<'input'>['size']
10
- visualSize?: 'default' | 'lg' | 'sm'
10
+ visualSize?: LumenControlVisualSize
11
11
  }
12
12
 
13
13
  const {