@santi020k/lumen-astro 1.6.0 → 1.7.0-rc.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.
- package/README.md +16 -1
- package/components/BarChart.astro +5 -1
- package/components/Chart.astro +3 -0
- package/components/ComboChart.astro +118 -0
- package/components/Heatmap.astro +32 -0
- package/components/Illustration.astro +8 -30
- package/components/Image.astro +8 -1
- package/components/LineChart.astro +5 -1
- package/components/PhoneInput.astro +70 -7
- package/components/PieChart.astro +11 -0
- package/components/RangeChart.astro +58 -0
- package/components/ScatterChart.astro +78 -0
- package/index.ts +4 -0
- package/package.json +3 -3
- package/runtime/UIPrimitives.astro +73 -1
package/README.md
CHANGED
|
@@ -60,6 +60,15 @@ incremental migrations, but new code should keep native behavior and visual styl
|
|
|
60
60
|
<NativeSelect size={8} visualSize="lg" />
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
`PhoneInput` generates localized country names, supplementary flags, and calling codes when custom
|
|
64
|
+
`countries` are not provided. Mount `UIPrimitives` once to enable as-you-type formatting,
|
|
65
|
+
metadata-backed validation, automatic country detection for pasted international numbers, and the
|
|
66
|
+
`ui:phone-change` event with a `LumenPhoneNumber` detail.
|
|
67
|
+
|
|
68
|
+
```astro
|
|
69
|
+
<PhoneInput countryValue="CO" locale="en-US" name="hospitalPhone" />
|
|
70
|
+
```
|
|
71
|
+
|
|
63
72
|
## Compound interactive components
|
|
64
73
|
|
|
65
74
|
Popover, dropdown menu, tabs, and tooltip parts expose the DOM and ARIA contracts consumed by the
|
|
@@ -216,7 +225,9 @@ non-code content `Code` semantics.
|
|
|
216
225
|
## Data visualization
|
|
217
226
|
|
|
218
227
|
Use `Sparkline` beside a metric, `BarChart` for categorical comparison, `LineChart` for ordered
|
|
219
|
-
trends,
|
|
228
|
+
trends, `PieChart` for a small part-to-whole breakdown, `ScatterChart` for numeric relationships,
|
|
229
|
+
`Heatmap` for a matrix, `RangeChart` for intervals, and `ComboChart` for a shared-domain mix of bars,
|
|
230
|
+
lines, and areas. The chart components accept
|
|
220
231
|
already-aggregated series, use Lumen data-color tokens, and include a revealable semantic data
|
|
221
232
|
table by default.
|
|
222
233
|
|
|
@@ -237,6 +248,10 @@ const series = [{
|
|
|
237
248
|
`PieChart` accepts one `LumenChartSeries`; its data points become slices. It defaults to
|
|
238
249
|
`variant="donut"` and also supports `variant="pie"`.
|
|
239
250
|
|
|
251
|
+
Every chart includes a factual screen-reader summary by default and accepts `summary` for more
|
|
252
|
+
useful domain context. See [data visualization](../../docs/data-visualization.md) for selection,
|
|
253
|
+
missing-data, live-data, and accessibility guidance.
|
|
254
|
+
|
|
240
255
|
Lucide intentionally excludes brand marks. For GitHub and other project-specific icons, put the
|
|
241
256
|
SVG in the default slot instead of passing an unsupported `name`; Astro warns about unknown named
|
|
242
257
|
icons during development.
|
|
@@ -3,6 +3,7 @@ import type { HTMLAttributes } from 'astro/types'
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
createLumenBarGeometry,
|
|
6
|
+
formatLumenChartSummary,
|
|
6
7
|
getLumenChartTicks,
|
|
7
8
|
getLumenChartToneClassName,
|
|
8
9
|
hasLumenChartData,
|
|
@@ -31,6 +32,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
31
32
|
series: readonly LumenChartSeries[]
|
|
32
33
|
showLegend?: boolean
|
|
33
34
|
showTable?: boolean
|
|
35
|
+
summary?: string
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const {
|
|
@@ -49,6 +51,7 @@ const {
|
|
|
49
51
|
series,
|
|
50
52
|
showLegend = series.length > 1,
|
|
51
53
|
showTable = true,
|
|
54
|
+
summary = formatLumenChartSummary(series, formatValue),
|
|
52
55
|
...rest
|
|
53
56
|
} = Astro.props
|
|
54
57
|
|
|
@@ -72,7 +75,7 @@ const labelFor = (item: LumenChartSeries, category: number | string) => (
|
|
|
72
75
|
const formattedValueFor = (item: LumenChartSeries, category: number | string) => {
|
|
73
76
|
const value = valueFor(item, category)
|
|
74
77
|
|
|
75
|
-
return value === null ? 'Not available' : formatValue(value)
|
|
78
|
+
return value === null || !Number.isFinite(value) ? 'Not available' : formatValue(value)
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
const categoryLabelFor = (category: number | string) => {
|
|
@@ -92,6 +95,7 @@ const margin = orientation === 'horizontal' ?
|
|
|
92
95
|
{description}
|
|
93
96
|
{glass}
|
|
94
97
|
{heading}
|
|
98
|
+
{summary}
|
|
95
99
|
{...rest}
|
|
96
100
|
>
|
|
97
101
|
{
|
package/components/Chart.astro
CHANGED
|
@@ -11,6 +11,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
11
11
|
glass?: boolean | 'subtle' | 'strong'
|
|
12
12
|
heading?: string
|
|
13
13
|
presentation?: 'bare' | 'default'
|
|
14
|
+
summary?: string
|
|
14
15
|
value?: string
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -22,6 +23,7 @@ const {
|
|
|
22
23
|
glass = false,
|
|
23
24
|
heading,
|
|
24
25
|
presentation = 'default',
|
|
26
|
+
summary,
|
|
25
27
|
value,
|
|
26
28
|
...rest
|
|
27
29
|
} = Astro.props
|
|
@@ -53,6 +55,7 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
53
55
|
</header>
|
|
54
56
|
)
|
|
55
57
|
}
|
|
58
|
+
{summary && <p class="ui-sr-only" data-ui-chart-summary>{summary}</p>}
|
|
56
59
|
<slot />
|
|
57
60
|
{caption && <figcaption>{caption}</figcaption>}
|
|
58
61
|
</figure>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
alignLumenChartSeries,
|
|
6
|
+
createLumenBarGeometry,
|
|
7
|
+
createLumenLineGeometry,
|
|
8
|
+
formatLumenChartSummary,
|
|
9
|
+
getLumenChartCategories,
|
|
10
|
+
getLumenChartDomain,
|
|
11
|
+
getLumenChartToneClassName,
|
|
12
|
+
hasLumenChartData,
|
|
13
|
+
type LumenChartDatum,
|
|
14
|
+
type LumenComboSeries,
|
|
15
|
+
resolveLumenChartTone } from '@santi020k/lumen-core'
|
|
16
|
+
|
|
17
|
+
import Chart from './Chart.astro'
|
|
18
|
+
|
|
19
|
+
interface Props extends HTMLAttributes<'figure'> {
|
|
20
|
+
caption?: string
|
|
21
|
+
class?: string | null
|
|
22
|
+
className?: string
|
|
23
|
+
description?: string
|
|
24
|
+
formatValue?: (value: number) => string
|
|
25
|
+
heading?: string
|
|
26
|
+
series: readonly LumenComboSeries[]
|
|
27
|
+
showLegend?: boolean
|
|
28
|
+
showTable?: boolean
|
|
29
|
+
summary?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { caption, class: classProp = '', className = '', description, formatValue = String, heading, series, showLegend = true, showTable = true, summary = formatLumenChartSummary(series, formatValue), ...rest } = Astro.props
|
|
33
|
+
const width = 640
|
|
34
|
+
const height = 320
|
|
35
|
+
const padding = 44
|
|
36
|
+
const categories = getLumenChartCategories(series)
|
|
37
|
+
|
|
38
|
+
const alignedSeries = series.map(item => ({
|
|
39
|
+
...item,
|
|
40
|
+
data: alignLumenChartSeries(item, categories).data
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
const domain = getLumenChartDomain(alignedSeries.flatMap(item => item.data.map(datum => datum.y)))
|
|
44
|
+
const barSeries = alignedSeries.filter(item => item.mark === 'bar')
|
|
45
|
+
const lineSeries = alignedSeries.filter(item => item.mark !== 'bar')
|
|
46
|
+
const bars = createLumenBarGeometry(barSeries, { domain, height, width })
|
|
47
|
+
|
|
48
|
+
const categoryPositions = new Map(
|
|
49
|
+
bars.categories.map(category => [`${typeof category.category}:${String(category.category)}`, category.x])
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const drawableWidth = width - padding * 2
|
|
53
|
+
|
|
54
|
+
const alignComboLineDatum = (datum: LumenChartDatum): LumenChartDatum => {
|
|
55
|
+
if (barSeries.length === 0) return datum
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...datum,
|
|
59
|
+
x: ((categoryPositions.get(`${typeof datum.x}:${String(datum.x)}`) ?? padding) - padding) / drawableWidth
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lineGeometryOptions = {
|
|
64
|
+
domain,
|
|
65
|
+
height,
|
|
66
|
+
padding,
|
|
67
|
+
width,
|
|
68
|
+
...(barSeries.length === 0 ?
|
|
69
|
+
{} :
|
|
70
|
+
{
|
|
71
|
+
xDomain: { max: 1, min: 0 },
|
|
72
|
+
xScale: 'linear' as const
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lines = lineSeries.map(item => createLumenLineGeometry(
|
|
77
|
+
item.data.map(alignComboLineDatum),
|
|
78
|
+
lineGeometryOptions
|
|
79
|
+
))
|
|
80
|
+
|
|
81
|
+
const hasData = hasLumenChartData(series)
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
<Chart class:list={['ui-combo-chart', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
85
|
+
{showLegend && hasData && <ul class="ui-chart__legend" aria-label="Chart legend">{series.map((item, index) => <li class={getLumenChartToneClassName(item.tone, index)}><span aria-hidden="true" />{item.label}</li>)}</ul>}
|
|
86
|
+
{!hasData && <p class="ui-chart__empty" role="status">No chart data available.</p>}
|
|
87
|
+
<div class="ui-chart__plot" hidden={!hasData}><svg aria-hidden="true" viewBox={`0 0 ${width} ${height}`}><g class="
|
|
88
|
+
ui-bar-chart__marks
|
|
89
|
+
"
|
|
90
|
+
>{bars.marks.map(mark => <rect class={getLumenChartToneClassName(mark.tone)} height={mark.height} rx="4" width={mark.width} x={mark.x} y={mark.y}><title>{`${mark.seriesLabel}: ${formatValue(mark.value)}`}</title></rect>)}</g>{lines.map((geometry, index) => {
|
|
91
|
+
const item = lineSeries[index]
|
|
92
|
+
|
|
93
|
+
if (!item) return null
|
|
94
|
+
|
|
95
|
+
const tone = resolveLumenChartTone(item.tone, index + barSeries.length)
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<g class:list={['ui-line-chart__series', getLumenChartToneClassName(tone)]}>{item.mark === 'area' && geometry.areaPaths.map(path => (
|
|
99
|
+
<path
|
|
100
|
+
class="ui-line-chart__area"
|
|
101
|
+
d={path}
|
|
102
|
+
/>
|
|
103
|
+
)) }<path
|
|
104
|
+
class="ui-line-chart__line"
|
|
105
|
+
d={geometry.path}
|
|
106
|
+
/></g>
|
|
107
|
+
)
|
|
108
|
+
})}</svg></div>
|
|
109
|
+
{showTable && hasData && (
|
|
110
|
+
<details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">Category</th>{series.map(item => <th scope="col">{item.label}</th>)}</tr></thead><tbody>{categories.map(category => (
|
|
111
|
+
<tr><th scope="row">{String(category)}</th>{series.map(item => {
|
|
112
|
+
const datum = item.data.find(point => point.x === category)
|
|
113
|
+
|
|
114
|
+
return <td>{datum?.label ?? (datum?.y === undefined || datum.y === null || !Number.isFinite(datum.y) ? 'Not available' : formatValue(datum.y))}</td>
|
|
115
|
+
})}</tr>
|
|
116
|
+
))}</tbody></table></div></details>
|
|
117
|
+
)}
|
|
118
|
+
</Chart>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { createLumenHeatmapGeometry, type LumenHeatmapDatum } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
import Chart from './Chart.astro'
|
|
7
|
+
|
|
8
|
+
interface Props extends HTMLAttributes<'figure'> {
|
|
9
|
+
caption?: string
|
|
10
|
+
class?: string | null
|
|
11
|
+
className?: string
|
|
12
|
+
data: readonly LumenHeatmapDatum[]
|
|
13
|
+
description?: string
|
|
14
|
+
formatValue?: (value: number) => string
|
|
15
|
+
heading?: string
|
|
16
|
+
showTable?: boolean
|
|
17
|
+
summary?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { caption, class: classProp = '', className = '', data, description, formatValue = String, heading, showTable = true, summary = `${data.filter(item => item.value !== null && Number.isFinite(item.value)).length} available heatmap cells.`, ...rest } = Astro.props
|
|
21
|
+
const geometry = createLumenHeatmapGeometry(data)
|
|
22
|
+
const hasData = geometry.cells.some(cell => cell.value !== null && Number.isFinite(cell.value))
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<Chart class:list={['ui-heatmap', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
26
|
+
{!hasData && <p class="ui-chart__empty" role="status">No chart data available.</p>}
|
|
27
|
+
<div class="ui-chart__plot" hidden={!hasData}><svg aria-hidden="true" viewBox={`0 0 ${geometry.width} ${geometry.height}`}><g class="
|
|
28
|
+
ui-heatmap__cells
|
|
29
|
+
"
|
|
30
|
+
>{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>
|
|
31
|
+
{showTable && <details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">Column</th><th scope="col">Row</th><th scope="col">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) ? 'Not available' : formatValue(cell.value))}</td></tr>)}</tbody></table></div></details>}
|
|
32
|
+
</Chart>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
renderLumenIllustrationSvg,
|
|
6
|
+
resolveLumenAstroProps
|
|
7
|
+
} from '@santi020k/lumen-core'
|
|
5
8
|
|
|
6
9
|
interface Props extends HTMLAttributes<'span'> {
|
|
7
10
|
class?: string | null
|
|
@@ -28,6 +31,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
28
31
|
`ui-illustration--${tone}`,
|
|
29
32
|
`ui-illustration--${size}`
|
|
30
33
|
], classProp, className)
|
|
34
|
+
|
|
35
|
+
const artwork = renderLumenIllustrationSvg(variant)
|
|
31
36
|
---
|
|
32
37
|
|
|
33
38
|
<span
|
|
@@ -36,32 +41,5 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
36
41
|
class:list={classList}
|
|
37
42
|
role={label ? 'img' : undefined}
|
|
38
43
|
{...passthrough}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<circle class="ui-illustration__wash" cx="60" cy="60" r="48" />
|
|
42
|
-
{variant === 'empty' && (
|
|
43
|
-
<g>
|
|
44
|
-
<path d="M32 48h56l-7 35H39l-7-35Z" />
|
|
45
|
-
<path d="M43 48 49 36h22l6 12M47 66h26" />
|
|
46
|
-
</g>
|
|
47
|
-
)}
|
|
48
|
-
{variant === 'success' && (
|
|
49
|
-
<g>
|
|
50
|
-
<circle cx="60" cy="60" r="28" />
|
|
51
|
-
<path d="m45 60 10 10 21-23" />
|
|
52
|
-
</g>
|
|
53
|
-
)}
|
|
54
|
-
{variant === 'error' && (
|
|
55
|
-
<g>
|
|
56
|
-
<circle cx="60" cy="60" r="28" />
|
|
57
|
-
<path d="m49 49 22 22m0-22L49 71" />
|
|
58
|
-
</g>
|
|
59
|
-
)}
|
|
60
|
-
{variant === 'offline' && (
|
|
61
|
-
<g>
|
|
62
|
-
<path d="M37 74h44a14 14 0 0 0 1-28 23 23 0 0 0-42-4 16 16 0 0 0-3 32Z" />
|
|
63
|
-
<path d="m38 34 44 52" />
|
|
64
|
-
</g>
|
|
65
|
-
)}
|
|
66
|
-
</svg>
|
|
67
|
-
</span>
|
|
44
|
+
set:html={artwork}
|
|
45
|
+
/>
|
package/components/Image.astro
CHANGED
|
@@ -10,25 +10,32 @@ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
|
|
|
10
10
|
type Props = (LocalImageProps | RemoteImageProps) & {
|
|
11
11
|
class?: string | null
|
|
12
12
|
className?: string
|
|
13
|
+
fit?: 'contain' | 'cover'
|
|
13
14
|
invertOnDark?: boolean
|
|
15
|
+
radius?: 'full' | 'lg' | 'md' | 'none' | 'sm'
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const {
|
|
17
19
|
class: classProp = '',
|
|
18
20
|
className = '',
|
|
21
|
+
fit = 'cover',
|
|
19
22
|
invertOnDark = false,
|
|
20
23
|
loading = 'lazy',
|
|
24
|
+
radius = 'lg',
|
|
21
25
|
...rest
|
|
22
26
|
} = Astro.props
|
|
23
27
|
|
|
24
28
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
25
29
|
'ui-image',
|
|
26
|
-
invertOnDark && 'ui-image--invert-dark'
|
|
30
|
+
invertOnDark && 'ui-image--invert-dark',
|
|
31
|
+
`ui-image--fit-${fit}`,
|
|
32
|
+
`ui-image--radius-${radius}`
|
|
27
33
|
], classProp, className)
|
|
28
34
|
---
|
|
29
35
|
|
|
30
36
|
<AstroImage
|
|
31
37
|
loading={loading}
|
|
32
38
|
class:list={classList}
|
|
39
|
+
fit={fit}
|
|
33
40
|
{...passthrough}
|
|
34
41
|
/>
|
|
@@ -4,6 +4,7 @@ import type { HTMLAttributes } from 'astro/types'
|
|
|
4
4
|
import {
|
|
5
5
|
alignLumenChartSeries,
|
|
6
6
|
createLumenLineGeometry,
|
|
7
|
+
formatLumenChartSummary,
|
|
7
8
|
getLumenChartCategories,
|
|
8
9
|
getLumenChartDomain,
|
|
9
10
|
getLumenChartTicks,
|
|
@@ -33,6 +34,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
33
34
|
series: readonly LumenChartSeries[]
|
|
34
35
|
showLegend?: boolean
|
|
35
36
|
showTable?: boolean
|
|
37
|
+
summary?: string
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const {
|
|
@@ -51,6 +53,7 @@ const {
|
|
|
51
53
|
series,
|
|
52
54
|
showLegend = series.length > 1,
|
|
53
55
|
showTable = true,
|
|
56
|
+
summary = formatLumenChartSummary(series, formatValue),
|
|
54
57
|
...rest
|
|
55
58
|
} = Astro.props
|
|
56
59
|
|
|
@@ -99,6 +102,7 @@ const referenceY =
|
|
|
99
102
|
{description}
|
|
100
103
|
{glass}
|
|
101
104
|
{heading}
|
|
105
|
+
{summary}
|
|
102
106
|
{...rest}
|
|
103
107
|
>
|
|
104
108
|
{
|
|
@@ -227,7 +231,7 @@ const referenceY =
|
|
|
227
231
|
return (
|
|
228
232
|
<td>
|
|
229
233
|
{datum?.label ??
|
|
230
|
-
(datum?.y ===
|
|
234
|
+
(datum?.y === undefined || datum.y === null || !Number.isFinite(datum.y) ?
|
|
231
235
|
'Not available' :
|
|
232
236
|
formatValue(datum.y))}
|
|
233
237
|
</td>
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getLumenPhoneCountries,
|
|
6
|
+
getLumenPhoneCountry,
|
|
7
|
+
type LumenPhoneCountry,
|
|
8
|
+
type LumenPhoneCountryOptions,
|
|
9
|
+
type LumenPhoneNumber,
|
|
10
|
+
resolveLumenAstroProps,
|
|
11
|
+
resolveLumenPhoneNumber
|
|
12
|
+
} from '@santi020k/lumen-core'
|
|
5
13
|
|
|
6
14
|
interface CountryOption {
|
|
7
15
|
disabled?: boolean
|
|
@@ -11,13 +19,20 @@ interface CountryOption {
|
|
|
11
19
|
|
|
12
20
|
type Country = CountryOption | string
|
|
13
21
|
|
|
22
|
+
interface NormalizedCountryOption extends CountryOption {
|
|
23
|
+
regionCode?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
14
26
|
interface Props extends Omit<HTMLAttributes<'div'>, 'size'> {
|
|
15
27
|
class?: string | null
|
|
16
28
|
className?: string
|
|
17
29
|
countries?: Country[]
|
|
30
|
+
countryOptions?: readonly LumenPhoneCountry[]
|
|
18
31
|
countryLabel?: string
|
|
19
32
|
countryName?: string
|
|
20
33
|
countryValue?: string
|
|
34
|
+
invalidNumberMessage?: string
|
|
35
|
+
locale?: LumenPhoneCountryOptions['locale']
|
|
21
36
|
name?: string
|
|
22
37
|
placeholder?: string
|
|
23
38
|
size?: 'default' | 'lg' | 'sm'
|
|
@@ -28,9 +43,12 @@ const {
|
|
|
28
43
|
class: classProp = '',
|
|
29
44
|
className = '',
|
|
30
45
|
countries = [],
|
|
46
|
+
countryOptions,
|
|
31
47
|
countryLabel = 'Country code',
|
|
32
48
|
countryName = 'country',
|
|
33
|
-
countryValue,
|
|
49
|
+
countryValue = 'US',
|
|
50
|
+
invalidNumberMessage = 'Enter a complete phone number.',
|
|
51
|
+
locale,
|
|
34
52
|
name = 'phone',
|
|
35
53
|
placeholder = 'Phone number',
|
|
36
54
|
size = 'default',
|
|
@@ -38,8 +56,45 @@ const {
|
|
|
38
56
|
...rest
|
|
39
57
|
} = Astro.props
|
|
40
58
|
|
|
41
|
-
const
|
|
42
|
-
const
|
|
59
|
+
const phoneOptions = locale === undefined ? {} : { locale }
|
|
60
|
+
const metadataCountries = countryOptions ?? getLumenPhoneCountries(phoneOptions)
|
|
61
|
+
const usesMetadata = countries.length === 0
|
|
62
|
+
|
|
63
|
+
const normalizedCountries: NormalizedCountryOption[] = usesMetadata ?
|
|
64
|
+
metadataCountries.map(country => ({
|
|
65
|
+
label: country.pickerLabel,
|
|
66
|
+
regionCode: country.regionCode,
|
|
67
|
+
value: country.regionCode
|
|
68
|
+
})) :
|
|
69
|
+
countries.map(country => typeof country === 'string' ? { label: country, value: country } : country)
|
|
70
|
+
|
|
71
|
+
const selectedCountry = metadataCountries.find(country => (
|
|
72
|
+
country.regionCode === countryValue || country.callingCode === countryValue
|
|
73
|
+
)) ?? metadataCountries[0] ?? getLumenPhoneCountry('US', phoneOptions)
|
|
74
|
+
|
|
75
|
+
const detectedPhoneValue = selectedCountry && value ?
|
|
76
|
+
resolveLumenPhoneNumber(selectedCountry, value, phoneOptions) :
|
|
77
|
+
null
|
|
78
|
+
|
|
79
|
+
const normalizePhoneValue = (detectedValue: LumenPhoneNumber | null): LumenPhoneNumber | null => {
|
|
80
|
+
if (!detectedValue || !selectedCountry) return detectedValue
|
|
81
|
+
|
|
82
|
+
const detectedCountryIsAllowed = metadataCountries.some(country => (
|
|
83
|
+
country.regionCode === detectedValue.country.regionCode
|
|
84
|
+
))
|
|
85
|
+
|
|
86
|
+
if (detectedCountryIsAllowed) return detectedValue
|
|
87
|
+
|
|
88
|
+
const allowedCountryValue = detectedValue.nationalNumber.startsWith('+') ?
|
|
89
|
+
detectedValue.nationalNumber.slice(1) :
|
|
90
|
+
detectedValue.nationalNumber
|
|
91
|
+
|
|
92
|
+
return resolveLumenPhoneNumber(selectedCountry, allowedCountryValue, phoneOptions)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const phoneValue = normalizePhoneValue(detectedPhoneValue)
|
|
96
|
+
const selectedCountryValue = usesMetadata ? phoneValue?.country.regionCode ?? selectedCountry?.regionCode : countryValue
|
|
97
|
+
const renderedValue = usesMetadata ? phoneValue?.nationalNumber ?? value : value
|
|
43
98
|
|
|
44
99
|
const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
45
100
|
'ui-phone-input',
|
|
@@ -47,7 +102,14 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
47
102
|
], classProp, className)
|
|
48
103
|
---
|
|
49
104
|
|
|
50
|
-
<div
|
|
105
|
+
<div
|
|
106
|
+
class:list={classList}
|
|
107
|
+
data-e164={phoneValue?.e164}
|
|
108
|
+
data-invalid-number-message={invalidNumberMessage}
|
|
109
|
+
data-ui-phone-input={usesMetadata ? '' : undefined}
|
|
110
|
+
data-valid={phoneValue ? String(phoneValue.isValid) : undefined}
|
|
111
|
+
{...passthrough}
|
|
112
|
+
>
|
|
51
113
|
<select
|
|
52
114
|
aria-label={countryLabel}
|
|
53
115
|
class:list={[
|
|
@@ -61,7 +123,8 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
61
123
|
{normalizedCountries.map(country => (
|
|
62
124
|
<option
|
|
63
125
|
disabled={country.disabled}
|
|
64
|
-
|
|
126
|
+
data-region={country.regionCode}
|
|
127
|
+
selected={country.value === selectedCountryValue}
|
|
65
128
|
value={country.value}
|
|
66
129
|
>{country.label}
|
|
67
130
|
</option>
|
|
@@ -79,6 +142,6 @@ const { classList, passthrough } = resolveLumenAstroProps(rest, [
|
|
|
79
142
|
name={name}
|
|
80
143
|
placeholder={placeholder}
|
|
81
144
|
type="tel"
|
|
82
|
-
value={
|
|
145
|
+
value={renderedValue}
|
|
83
146
|
/>
|
|
84
147
|
</div>
|
|
@@ -3,6 +3,7 @@ import type { HTMLAttributes } from 'astro/types'
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
createLumenPieGeometry,
|
|
6
|
+
formatLumenChartSummary,
|
|
6
7
|
getLumenChartToneClassName,
|
|
7
8
|
getLumenPieChartVariantClassName,
|
|
8
9
|
hasLumenPieData,
|
|
@@ -24,6 +25,7 @@ interface Props extends HTMLAttributes<'figure'> {
|
|
|
24
25
|
series: LumenChartSeries
|
|
25
26
|
showLegend?: boolean
|
|
26
27
|
showTable?: boolean
|
|
28
|
+
summary?: string
|
|
27
29
|
valueFormatter?: (value: number) => string
|
|
28
30
|
variant?: LumenPieChartVariant
|
|
29
31
|
}
|
|
@@ -41,6 +43,7 @@ const {
|
|
|
41
43
|
showLegend = true,
|
|
42
44
|
showTable = true,
|
|
43
45
|
valueFormatter = String,
|
|
46
|
+
summary: summaryProp,
|
|
44
47
|
variant = 'donut',
|
|
45
48
|
...rest
|
|
46
49
|
} = Astro.props
|
|
@@ -48,6 +51,13 @@ const {
|
|
|
48
51
|
const geometry = createLumenPieGeometry(series.data, { variant })
|
|
49
52
|
const hasData = hasLumenPieData(series.data)
|
|
50
53
|
|
|
54
|
+
const renderedSeries = {
|
|
55
|
+
...series,
|
|
56
|
+
data: series.data.filter(datum => datum.y !== null && Number.isFinite(datum.y) && datum.y > 0)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const summary = summaryProp ?? formatLumenChartSummary([renderedSeries], valueFormatter)
|
|
60
|
+
|
|
51
61
|
const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
|
|
52
62
|
maximumFractionDigits: percentage < 0.01 ? 1 : 0,
|
|
53
63
|
style: 'percent'
|
|
@@ -60,6 +70,7 @@ const percentageFor = (percentage: number) => new Intl.NumberFormat(undefined, {
|
|
|
60
70
|
{description}
|
|
61
71
|
{glass}
|
|
62
72
|
{heading}
|
|
73
|
+
{summary}
|
|
63
74
|
{...rest}
|
|
64
75
|
>
|
|
65
76
|
{
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import { createLumenRangeGeometry, getLumenChartTicks, getLumenChartToneClassName, type LumenChartTone, type LumenRangeDatum, scaleLumenChartValue } from '@santi020k/lumen-core'
|
|
5
|
+
|
|
6
|
+
import Chart from './Chart.astro'
|
|
7
|
+
|
|
8
|
+
interface Props extends HTMLAttributes<'figure'> {
|
|
9
|
+
caption?: string
|
|
10
|
+
class?: string | null
|
|
11
|
+
className?: string
|
|
12
|
+
data: readonly LumenRangeDatum[]
|
|
13
|
+
description?: string
|
|
14
|
+
formatValue?: (value: number) => string
|
|
15
|
+
heading?: string
|
|
16
|
+
showTable?: boolean
|
|
17
|
+
summary?: string
|
|
18
|
+
tone?: LumenChartTone
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { caption, class: classProp = '', className = '', data, description, formatValue = String, heading, showTable = true, summary: summaryProp, tone = 'series-1', ...rest } = Astro.props
|
|
22
|
+
const width = 640
|
|
23
|
+
const height = 320
|
|
24
|
+
const padding = 44
|
|
25
|
+
const geometry = createLumenRangeGeometry(data, { height, padding, width })
|
|
26
|
+
|
|
27
|
+
const summary = summaryProp ?? (
|
|
28
|
+
geometry.points.length === 0 ?
|
|
29
|
+
'No chart data available.' :
|
|
30
|
+
`${geometry.points.length} available ranges.`
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const ticks = getLumenChartTicks(geometry.domain)
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<Chart class:list={['ui-range-chart', getLumenChartToneClassName(tone), classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
37
|
+
{geometry.points.length === 0 && <p class="ui-chart__empty" role="status">No chart data available.</p>}
|
|
38
|
+
<div class="ui-chart__plot" hidden={geometry.points.length === 0}><svg aria-hidden="true" viewBox={`0 0 ${width} ${height}`}><g class="
|
|
39
|
+
ui-chart__grid
|
|
40
|
+
"
|
|
41
|
+
>{ticks.map(tick => {
|
|
42
|
+
const y = scaleLumenChartValue(tick, geometry.domain, height - padding, padding)
|
|
43
|
+
|
|
44
|
+
return <><line x1={padding} x2={width - padding} y1={y} y2={y}/><text x={padding - 8} y={y}>{formatValue(tick)}</text></>
|
|
45
|
+
})}</g><path
|
|
46
|
+
class="ui-range-chart__area"
|
|
47
|
+
d={geometry.areaPath}
|
|
48
|
+
/>{geometry.points.map(point => (
|
|
49
|
+
<line
|
|
50
|
+
class="ui-range-chart__interval"
|
|
51
|
+
x1={point.xCoordinate}
|
|
52
|
+
x2={point.xCoordinate}
|
|
53
|
+
y1={point.highCoordinate}
|
|
54
|
+
y2={point.lowCoordinate}
|
|
55
|
+
><title>{`${point.xLabel ?? point.x}: ${point.label ?? `${formatValue(point.low ?? 0)}–${formatValue(point.high ?? 0)}`}`}</title></line>
|
|
56
|
+
))}</svg></div>
|
|
57
|
+
{showTable && <details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">Category</th><th scope="col">Low</th><th scope="col">High</th></tr></thead><tbody>{data.map(item => <tr><th scope="row">{item.xLabel ?? item.x}</th><td>{item.low === null || !Number.isFinite(item.low) ? 'Not available' : formatValue(item.low)}</td><td>{item.high === null || !Number.isFinite(item.high) ? 'Not available' : formatValue(item.high)}</td></tr>)}</tbody></table></div></details>}
|
|
58
|
+
</Chart>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createLumenScatterGeometry,
|
|
6
|
+
formatLumenChartSummary,
|
|
7
|
+
getLumenChartTicks,
|
|
8
|
+
getLumenChartToneClassName,
|
|
9
|
+
type LumenChartScaleType,
|
|
10
|
+
type LumenChartSeries,
|
|
11
|
+
scaleLumenChartValue } from '@santi020k/lumen-core'
|
|
12
|
+
|
|
13
|
+
import Chart from './Chart.astro'
|
|
14
|
+
|
|
15
|
+
interface Props extends HTMLAttributes<'figure'> {
|
|
16
|
+
caption?: string
|
|
17
|
+
class?: string | null
|
|
18
|
+
className?: string
|
|
19
|
+
description?: string
|
|
20
|
+
emptyLabel?: string
|
|
21
|
+
formatValue?: (value: number) => string
|
|
22
|
+
heading?: string
|
|
23
|
+
series: readonly LumenChartSeries[]
|
|
24
|
+
showLegend?: boolean
|
|
25
|
+
showTable?: boolean
|
|
26
|
+
summary?: string
|
|
27
|
+
xScale?: Exclude<LumenChartScaleType, 'categorical'>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
caption,
|
|
32
|
+
class: classProp = '',
|
|
33
|
+
className = '',
|
|
34
|
+
description,
|
|
35
|
+
emptyLabel = 'No chart data available.',
|
|
36
|
+
formatValue = String,
|
|
37
|
+
heading,
|
|
38
|
+
series,
|
|
39
|
+
showLegend = series.length > 1,
|
|
40
|
+
showTable = true,
|
|
41
|
+
summary: summaryProp,
|
|
42
|
+
xScale = 'linear',
|
|
43
|
+
...rest
|
|
44
|
+
} = Astro.props
|
|
45
|
+
|
|
46
|
+
const geometry = createLumenScatterGeometry(series, { xScale })
|
|
47
|
+
|
|
48
|
+
const projectedSeries = series.map(item => ({
|
|
49
|
+
...item,
|
|
50
|
+
data: geometry.points.filter(point => point.seriesId === item.id)
|
|
51
|
+
})).filter(item => item.data.length > 0)
|
|
52
|
+
|
|
53
|
+
const hasData = geometry.points.length > 0
|
|
54
|
+
const summary = summaryProp ?? formatLumenChartSummary(projectedSeries, formatValue)
|
|
55
|
+
const xTicks = getLumenChartTicks(geometry.xDomain)
|
|
56
|
+
const yTicks = getLumenChartTicks(geometry.domain)
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<Chart class:list={['ui-scatter-chart', classProp, className]} {caption} {description} {heading} {summary} {...rest}>
|
|
60
|
+
{showLegend && hasData && <ul class="ui-chart__legend" aria-label="Chart legend">{series.map((item, index) => <li class={getLumenChartToneClassName(item.tone, index)}><span aria-hidden="true" />{item.label}</li>)}</ul>}
|
|
61
|
+
{!hasData && <p class="ui-chart__empty" role="status">{emptyLabel}</p>}
|
|
62
|
+
<div class="ui-chart__plot" hidden={!hasData}>
|
|
63
|
+
<svg aria-hidden="true" viewBox={`0 0 ${geometry.width} ${geometry.height}`}>
|
|
64
|
+
<g class="ui-chart__grid">{yTicks.map(tick => {
|
|
65
|
+
const y = scaleLumenChartValue(tick, geometry.domain, geometry.height - 44, 44)
|
|
66
|
+
|
|
67
|
+
return <><line x1="44" x2={geometry.width - 44} y1={y} y2={y}/><text x="36" y={y}>{formatValue(tick)}</text></>
|
|
68
|
+
})}</g>
|
|
69
|
+
<g class="ui-chart__axis-labels">{xTicks.map(tick => {
|
|
70
|
+
const x = scaleLumenChartValue(tick, geometry.xDomain, 44, geometry.width - 44)
|
|
71
|
+
|
|
72
|
+
return <text text-anchor="middle" x={x} y={geometry.height - 14}>{formatValue(tick)}</text>
|
|
73
|
+
})}</g>
|
|
74
|
+
<g class="ui-scatter-chart__marks">{geometry.points.map(point => <circle class={getLumenChartToneClassName(point.tone)} cx={point.xCoordinate} cy={point.yCoordinate} r={point.radius}><title>{`${point.xLabel ?? point.x} · ${point.seriesLabel}: ${point.label ?? formatValue(point.y ?? 0)}`}</title></circle>)}</g>
|
|
75
|
+
</svg>
|
|
76
|
+
</div>
|
|
77
|
+
{showTable && hasData && <details class="ui-chart__data"><summary>View chart data</summary><div><table><thead><tr><th scope="col">X</th><th scope="col">Series</th><th scope="col">Value</th><th scope="col">Size</th></tr></thead><tbody>{geometry.points.map(point => <tr><th scope="row">{point.xLabel ?? point.x}</th><td>{point.seriesLabel}</td><td>{point.label ?? formatValue(point.y ?? 0)}</td><td>{point.size === undefined || point.size === null || !Number.isFinite(point.size) ? 'Not available' : formatValue(point.size)}</td></tr>)}</tbody></table></div></details>}
|
|
78
|
+
</Chart>
|
package/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ export { default as CodeTabs } from './components/CodeTabs.astro'
|
|
|
38
38
|
export { default as Collapsible } from './components/Collapsible.astro'
|
|
39
39
|
export { default as ColorPicker } from './components/ColorPicker.astro'
|
|
40
40
|
export { default as Combobox } from './components/Combobox.astro'
|
|
41
|
+
export { default as ComboChart } from './components/ComboChart.astro'
|
|
41
42
|
export { default as Command } from './components/Command.astro'
|
|
42
43
|
export { default as Container } from './components/Container.astro'
|
|
43
44
|
export { default as ContextMenu } from './components/ContextMenu.astro'
|
|
@@ -68,6 +69,7 @@ export { default as FormattedDate } from './components/FormattedDate.astro'
|
|
|
68
69
|
export { default as GradientDivider } from './components/GradientDivider.astro'
|
|
69
70
|
export { default as Graphic } from './components/Graphic.astro'
|
|
70
71
|
export { default as Grid } from './components/Grid.astro'
|
|
72
|
+
export { default as Heatmap } from './components/Heatmap.astro'
|
|
71
73
|
export { default as HoverCard } from './components/HoverCard.astro'
|
|
72
74
|
export { default as Icon } from './components/Icon.astro'
|
|
73
75
|
export { default as Illustration } from './components/Illustration.astro'
|
|
@@ -108,10 +110,12 @@ export { default as Progress } from './components/Progress.astro'
|
|
|
108
110
|
export { default as Prose } from './components/Prose.astro'
|
|
109
111
|
export { default as QRCode } from './components/QRCode.astro'
|
|
110
112
|
export { default as RadioGroup } from './components/RadioGroup.astro'
|
|
113
|
+
export { default as RangeChart } from './components/RangeChart.astro'
|
|
111
114
|
export { default as Rating } from './components/Rating.astro'
|
|
112
115
|
export { default as Resizable } from './components/Resizable.astro'
|
|
113
116
|
export { default as RevealGroup } from './components/RevealGroup.astro'
|
|
114
117
|
export { default as RichTextEditor } from './components/RichTextEditor.astro'
|
|
118
|
+
export { default as ScatterChart } from './components/ScatterChart.astro'
|
|
115
119
|
export { default as Schedule } from './components/Schedule.astro'
|
|
116
120
|
export { default as ScrollArea } from './components/ScrollArea.astro'
|
|
117
121
|
export { default as ScrollProgress } from './components/ScrollProgress.astro'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@santi020k/lumen-astro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0-rc.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"uqr": "^0.1.3",
|
|
40
|
-
"@santi020k/lumen-core": "^1.
|
|
41
|
-
"@santi020k/lumen": "^1.
|
|
40
|
+
"@santi020k/lumen-core": "^1.7.0-rc.0",
|
|
41
|
+
"@santi020k/lumen": "^1.7.0-rc.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@astrojs/ts-plugin": "^1.10.11",
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
exportThemeFigmaVariables,
|
|
18
18
|
formatLumenLanguageLabel,
|
|
19
19
|
getLumenLocalePair,
|
|
20
|
+
getLumenPhoneCountry,
|
|
20
21
|
getLumenRichTextShortcut,
|
|
21
22
|
isLumenRichTextToggleCommand,
|
|
22
23
|
type LumenLocaleOption,
|
|
@@ -24,7 +25,8 @@ import {
|
|
|
24
25
|
type LumenRichTextCommandDetail,
|
|
25
26
|
type LumenThemeTokens,
|
|
26
27
|
lumenTokenNames,
|
|
27
|
-
normalizeLumenLocales
|
|
28
|
+
normalizeLumenLocales,
|
|
29
|
+
resolveLumenPhoneNumber } from '@santi020k/lumen-core'
|
|
28
30
|
|
|
29
31
|
/* eslint-disable complexity */
|
|
30
32
|
/* Stateful DOM handlers stay local so Astro consumers do not need a framework runtime. */
|
|
@@ -71,6 +73,74 @@ const formControlSelector = [
|
|
|
71
73
|
'textarea'
|
|
72
74
|
].join(',')
|
|
73
75
|
|
|
76
|
+
const phoneInputSelector = '[data-ui-phone-input]'
|
|
77
|
+
|
|
78
|
+
const initPhoneInputs = (scope: ParentNode): void => {
|
|
79
|
+
for (const root of scope.querySelectorAll<HTMLElement>(phoneInputSelector)) {
|
|
80
|
+
if (root.dataset.uiPhoneBound === 'true') continue
|
|
81
|
+
|
|
82
|
+
const countrySelect = root.querySelector<HTMLSelectElement>('.ui-phone-input__country')
|
|
83
|
+
const numberInput = root.querySelector<HTMLInputElement>('.ui-phone-input__number')
|
|
84
|
+
|
|
85
|
+
if (!countrySelect || !numberInput) continue
|
|
86
|
+
|
|
87
|
+
root.dataset.uiPhoneBound = 'true'
|
|
88
|
+
|
|
89
|
+
const locale = root.lang || document.documentElement.lang || undefined
|
|
90
|
+
const phoneOptions = locale ? { locale } : {}
|
|
91
|
+
|
|
92
|
+
const commit = (): void => {
|
|
93
|
+
const selectedOption = countrySelect.selectedOptions[0]
|
|
94
|
+
const regionCode = selectedOption?.dataset.region ?? countrySelect.value
|
|
95
|
+
const country = getLumenPhoneCountry(regionCode, phoneOptions)
|
|
96
|
+
|
|
97
|
+
if (!country) return
|
|
98
|
+
|
|
99
|
+
const detectedPhoneNumber = resolveLumenPhoneNumber(country, numberInput.value, phoneOptions)
|
|
100
|
+
|
|
101
|
+
const detectedCountryIsAllowed = Array.from(countrySelect.options).some(option => (
|
|
102
|
+
option.value === detectedPhoneNumber.country.regionCode
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
const phoneNumber = detectedCountryIsAllowed ?
|
|
106
|
+
detectedPhoneNumber :
|
|
107
|
+
resolveLumenPhoneNumber(
|
|
108
|
+
country,
|
|
109
|
+
detectedPhoneNumber.nationalNumber.startsWith('+') ?
|
|
110
|
+
detectedPhoneNumber.nationalNumber.slice(1) :
|
|
111
|
+
detectedPhoneNumber.nationalNumber,
|
|
112
|
+
phoneOptions
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const hasInput = phoneNumber.nationalNumber.length > 0
|
|
116
|
+
const invalidMessage = root.dataset.invalidNumberMessage ?? 'Enter a complete phone number.'
|
|
117
|
+
|
|
118
|
+
numberInput.value = phoneNumber.nationalNumber
|
|
119
|
+
|
|
120
|
+
countrySelect.value = phoneNumber.country.regionCode
|
|
121
|
+
|
|
122
|
+
numberInput.setCustomValidity(hasInput && !phoneNumber.isValid ? invalidMessage : '')
|
|
123
|
+
|
|
124
|
+
numberInput.setAttribute('aria-invalid', String(hasInput && !phoneNumber.isValid))
|
|
125
|
+
|
|
126
|
+
root.dataset.e164 = phoneNumber.e164 ?? ''
|
|
127
|
+
|
|
128
|
+
root.dataset.valid = String(phoneNumber.isValid)
|
|
129
|
+
|
|
130
|
+
root.dispatchEvent(new CustomEvent('ui:phone-change', {
|
|
131
|
+
bubbles: true,
|
|
132
|
+
detail: phoneNumber
|
|
133
|
+
}))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
countrySelect.addEventListener('change', commit)
|
|
137
|
+
|
|
138
|
+
numberInput.addEventListener('input', commit)
|
|
139
|
+
|
|
140
|
+
if (numberInput.value) commit()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
74
144
|
const validityMessageAttributes = [
|
|
75
145
|
['valueMissing', 'data-error-required'],
|
|
76
146
|
['typeMismatch', 'data-error-type'],
|
|
@@ -5583,6 +5653,8 @@ const initUiPrimitives = (scope: ParentNode = document): void => {
|
|
|
5583
5653
|
|
|
5584
5654
|
initFields(scope)
|
|
5585
5655
|
|
|
5656
|
+
initPhoneInputs(scope)
|
|
5657
|
+
|
|
5586
5658
|
initForms(scope)
|
|
5587
5659
|
|
|
5588
5660
|
initPasswordFields(scope)
|