@adia-ai/web-components 0.6.36 → 0.6.37
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/CHANGELOG.md +28 -1
- package/components/badge/badge.a2ui.json +10 -0
- package/components/badge/badge.css +70 -0
- package/components/badge/badge.yaml +20 -0
- package/components/blockquote/blockquote.a2ui.json +121 -0
- package/components/blockquote/blockquote.class.js +68 -0
- package/components/blockquote/blockquote.css +46 -0
- package/components/blockquote/blockquote.d.ts +31 -0
- package/components/blockquote/blockquote.js +17 -0
- package/components/blockquote/blockquote.yaml +124 -0
- package/components/button/button.css +11 -3
- package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
- package/components/calendar-picker/calendar-picker.class.js +7 -1
- package/components/calendar-picker/calendar-picker.yaml +14 -0
- package/components/color-input/color-input.a2ui.json +2 -2
- package/components/color-input/color-input.class.js +9 -2
- package/components/color-input/color-input.yaml +2 -2
- package/components/combobox/combobox.class.js +4 -0
- package/components/context-menu/context-menu.a2ui.json +159 -0
- package/components/context-menu/context-menu.class.js +275 -0
- package/components/context-menu/context-menu.css +56 -0
- package/components/context-menu/context-menu.d.ts +70 -0
- package/components/context-menu/context-menu.js +17 -0
- package/components/context-menu/context-menu.yaml +136 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
- package/components/date-range-picker/date-range-picker.class.js +2 -0
- package/components/date-range-picker/date-range-picker.yaml +14 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
- package/components/datetime-picker/datetime-picker.class.js +3 -1
- package/components/datetime-picker/datetime-picker.d.ts +2 -0
- package/components/datetime-picker/datetime-picker.yaml +14 -0
- package/components/empty-state/empty-state.class.js +2 -0
- package/components/feed/feed.class.js +13 -5
- package/components/feed/feed.css +14 -0
- package/components/index.js +9 -0
- package/components/integration-card/integration-card.class.js +9 -0
- package/components/integration-card/integration-card.test.js +4 -3
- package/components/nav-group/nav-group.css +7 -1
- package/components/number-format/number-format.a2ui.json +180 -0
- package/components/number-format/number-format.class.js +96 -0
- package/components/number-format/number-format.css +18 -0
- package/components/number-format/number-format.d.ts +68 -0
- package/components/number-format/number-format.js +17 -0
- package/components/number-format/number-format.yaml +204 -0
- package/components/pagination/pagination.a2ui.json +19 -2
- package/components/pagination/pagination.class.js +90 -37
- package/components/pagination/pagination.css +32 -127
- package/components/pagination/pagination.d.ts +8 -2
- package/components/pagination/pagination.test.js +195 -0
- package/components/pagination/pagination.yaml +22 -1
- package/components/password-strength/password-strength.a2ui.json +152 -0
- package/components/password-strength/password-strength.class.js +157 -0
- package/components/password-strength/password-strength.css +80 -0
- package/components/password-strength/password-strength.d.ts +59 -0
- package/components/password-strength/password-strength.js +17 -0
- package/components/password-strength/password-strength.yaml +153 -0
- package/components/popover/popover.css +43 -23
- package/components/popover/popover.yaml +8 -4
- package/components/qr-code/QR-TEST.svg +4 -0
- package/components/qr-code/qr-code.a2ui.json +154 -0
- package/components/qr-code/qr-code.class.js +129 -0
- package/components/qr-code/qr-code.css +41 -0
- package/components/qr-code/qr-code.d.ts +83 -0
- package/components/qr-code/qr-code.js +17 -0
- package/components/qr-code/qr-code.yaml +203 -0
- package/components/qr-code/qr-encoder.js +633 -0
- package/components/relative-time/relative-time.a2ui.json +120 -0
- package/components/relative-time/relative-time.class.js +136 -0
- package/components/relative-time/relative-time.css +22 -0
- package/components/relative-time/relative-time.d.ts +51 -0
- package/components/relative-time/relative-time.js +17 -0
- package/components/relative-time/relative-time.yaml +133 -0
- package/components/segmented/segmented.class.js +5 -1
- package/components/select/select.class.js +4 -0
- package/components/skip-nav/skip-nav.a2ui.json +92 -0
- package/components/skip-nav/skip-nav.class.js +45 -0
- package/components/skip-nav/skip-nav.css +54 -0
- package/components/skip-nav/skip-nav.d.ts +27 -0
- package/components/skip-nav/skip-nav.js +12 -0
- package/components/skip-nav/skip-nav.yaml +68 -0
- package/components/slider/slider.a2ui.json +16 -1
- package/components/slider/slider.class.js +264 -122
- package/components/slider/slider.css +82 -2
- package/components/slider/slider.d.ts +19 -3
- package/components/slider/slider.test.js +55 -0
- package/components/slider/slider.yaml +28 -6
- package/components/table/table.class.js +29 -6
- package/components/table/table.css +31 -4
- package/components/table-toolbar/table-toolbar.class.js +3 -1
- package/components/tag/tag.a2ui.json +3 -2
- package/components/tag/tag.css +35 -11
- package/components/tag/tag.d.ts +14 -0
- package/components/tag/tag.test.js +35 -11
- package/components/tag/tag.yaml +13 -7
- package/components/toast/toast.class.js +12 -4
- package/components/toc/toc.a2ui.json +159 -0
- package/components/toc/toc.class.js +222 -0
- package/components/toc/toc.css +92 -0
- package/components/toc/toc.d.ts +61 -0
- package/components/toc/toc.js +17 -0
- package/components/toc/toc.yaml +180 -0
- package/components/toolbar/toolbar.class.js +3 -0
- package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
- package/components/visually-hidden/visually-hidden.class.js +14 -0
- package/components/visually-hidden/visually-hidden.css +25 -0
- package/components/visually-hidden/visually-hidden.d.ts +26 -0
- package/components/visually-hidden/visually-hidden.js +12 -0
- package/components/visually-hidden/visually-hidden.yaml +54 -0
- package/core/anchor.js +19 -3
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +100 -89
- package/package.json +1 -1
- package/styles/colors/semantics.css +11 -2
- package/styles/components.css +9 -0
- package/styles/resets.css +10 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<number-format-ui>`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file gives you the class without auto-registering the
|
|
5
|
+
* tag. Useful for test isolation, subclassing with tag-name override,
|
|
6
|
+
* or selective composition.
|
|
7
|
+
*
|
|
8
|
+
* The auto-register path stays at `@adia-ai/web-components/components/number-format`
|
|
9
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
10
|
+
*
|
|
11
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* <number-format-ui value="1234567.89"></number-format-ui> → 1,234,567.89
|
|
16
|
+
* <number-format-ui value="1299.99" number-style="currency" currency="USD"></number-format-ui> → $1,299.99
|
|
17
|
+
* <number-format-ui value="0.876" number-style="percent"></number-format-ui> → 87.6%
|
|
18
|
+
* <number-format-ui value="1234567" notation="compact"></number-format-ui> → 1.2M
|
|
19
|
+
*
|
|
20
|
+
* Read-only display primitive wrapping `Intl.NumberFormat`. Distinct
|
|
21
|
+
* from `<input-ui type="number">` (a form-input primitive).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { UIElement } from '../../core/element.js';
|
|
25
|
+
|
|
26
|
+
export class UINumberFormat extends UIElement {
|
|
27
|
+
static properties = {
|
|
28
|
+
value: { type: Number, default: 0, reflect: true },
|
|
29
|
+
numberStyle: { type: String, default: 'decimal', reflect: true, attribute: 'number-style' },
|
|
30
|
+
currency: { type: String, default: '', reflect: true },
|
|
31
|
+
unit: { type: String, default: '', reflect: true },
|
|
32
|
+
notation: { type: String, default: 'standard', reflect: true },
|
|
33
|
+
compactDisplay: { type: String, default: 'short', reflect: true, attribute: 'compact-display' },
|
|
34
|
+
minimumFractionDigits: { type: Number, default: 0, reflect: true, attribute: 'minimum-fraction-digits' },
|
|
35
|
+
maximumFractionDigits: { type: Number, default: 2, reflect: true, attribute: 'maximum-fraction-digits' },
|
|
36
|
+
locale: { type: String, default: '', reflect: true },
|
|
37
|
+
signDisplay: { type: String, default: 'auto', reflect: true, attribute: 'sign-display' },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
static template = () => null;
|
|
41
|
+
|
|
42
|
+
#resolveLocale() {
|
|
43
|
+
if (this.locale) return this.locale;
|
|
44
|
+
return this.ownerDocument?.documentElement?.lang || undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#format() {
|
|
48
|
+
const v = Number(this.value);
|
|
49
|
+
if (!Number.isFinite(v)) return '';
|
|
50
|
+
|
|
51
|
+
// Validate required dependent props per style.
|
|
52
|
+
if (this.numberStyle === 'currency' && !this.currency) return '';
|
|
53
|
+
if (this.numberStyle === 'unit' && !this.unit) return '';
|
|
54
|
+
|
|
55
|
+
/** @type {Intl.NumberFormatOptions} */
|
|
56
|
+
const opts = {
|
|
57
|
+
style: this.numberStyle,
|
|
58
|
+
notation: this.notation,
|
|
59
|
+
signDisplay: this.signDisplay,
|
|
60
|
+
};
|
|
61
|
+
if (this.numberStyle === 'currency') opts.currency = this.currency;
|
|
62
|
+
if (this.numberStyle === 'unit') opts.unit = this.unit;
|
|
63
|
+
if (this.notation === 'compact') opts.compactDisplay = this.compactDisplay;
|
|
64
|
+
// Honor min/max fraction digits when explicitly set. The defaults
|
|
65
|
+
// (0 / 2) are reasonable for decimal+currency; compact notation
|
|
66
|
+
// typically wants 1 max but we let consumers override.
|
|
67
|
+
if (Number.isFinite(this.minimumFractionDigits)) opts.minimumFractionDigits = this.minimumFractionDigits;
|
|
68
|
+
if (Number.isFinite(this.maximumFractionDigits)) opts.maximumFractionDigits = this.maximumFractionDigits;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
return new Intl.NumberFormat(this.#resolveLocale(), opts).format(v);
|
|
72
|
+
} catch {
|
|
73
|
+
return String(v);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
connected() {
|
|
78
|
+
super.connected();
|
|
79
|
+
// No special role — the rendered text is read by AT naturally; we
|
|
80
|
+
// expose the raw value via aria-label for screen-reader clarity in
|
|
81
|
+
// contexts where the formatted glyphs ("€", "M") might be parsed
|
|
82
|
+
// unpredictably by different readers.
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
render() {
|
|
86
|
+
const text = this.#format();
|
|
87
|
+
this.textContent = text;
|
|
88
|
+
// Mirror raw value via aria-label so screen readers can read the
|
|
89
|
+
// underlying number alongside the formatted glyphs.
|
|
90
|
+
if (text && Number.isFinite(Number(this.value))) {
|
|
91
|
+
this.setAttribute('aria-label', `${text} (raw: ${this.value})`);
|
|
92
|
+
} else {
|
|
93
|
+
this.removeAttribute('aria-label');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
+
NUMBER-FORMAT-UI — Read-only formatted numeric display.
|
|
3
|
+
═══════════════════════════════════════════════════════════════ */
|
|
4
|
+
|
|
5
|
+
@scope (number-format-ui) {
|
|
6
|
+
:where(:scope) {
|
|
7
|
+
--number-format-fg-default: inherit;
|
|
8
|
+
--number-format-font-default: inherit;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
:scope {
|
|
12
|
+
display: inline;
|
|
13
|
+
color: var(--number-format-fg, var(--number-format-fg-default));
|
|
14
|
+
font-family: var(--number-format-font, var(--number-format-font-default));
|
|
15
|
+
/* Tabular figures so column-aligned numeric displays line up cleanly. */
|
|
16
|
+
font-variant-numeric: tabular-nums;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<number-format-ui>` — Display a numeric value with locale-aware formatting — currency,
|
|
3
|
+
percentage, compact (1.2K / 3.4M), unit, or plain decimal. Wraps
|
|
4
|
+
`Intl.NumberFormat`. Distinct from `<input-ui type="number">`
|
|
5
|
+
(an INPUT primitive); this is a DISPLAY primitive — read-only, no
|
|
6
|
+
form participation, no keyboard handling. Pair with `<stat-ui>` for
|
|
7
|
+
KPI surfaces or use standalone inline within prose.
|
|
8
|
+
|
|
9
|
+
*
|
|
10
|
+
* @see https://ui-kit.exe.xyz/site/components/number-format
|
|
11
|
+
*
|
|
12
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
13
|
+
* the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
|
|
14
|
+
* run `npm run build:components`, then `npm run codegen:dts` to
|
|
15
|
+
* regenerate; or hand-author this file fully if rich event types are
|
|
16
|
+
* needed beyond what the yaml `events:` block can express.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { UIElement } from '../../core/element.js';
|
|
20
|
+
|
|
21
|
+
export class UINumberFormat extends UIElement {
|
|
22
|
+
/** When [notation="compact"], controls the compact-form length.
|
|
23
|
+
`short` = "1.2M" (default); `long` = "1.2 million".
|
|
24
|
+
*/
|
|
25
|
+
compactDisplay: 'short' | 'long';
|
|
26
|
+
/** ISO 4217 currency code (e.g. "USD", "EUR", "JPY"). Required when
|
|
27
|
+
[number-style="currency"]; ignored otherwise.
|
|
28
|
+
*/
|
|
29
|
+
currency: string;
|
|
30
|
+
/** BCP-47 locale tag for the formatter. Empty defaults to the
|
|
31
|
+
document locale (`<html lang>`) then to browser default.
|
|
32
|
+
*/
|
|
33
|
+
locale: string;
|
|
34
|
+
/** Maximum fractional digits (0–20). Default `2` for decimal/
|
|
35
|
+
currency/percent, `1` for compact notation.
|
|
36
|
+
*/
|
|
37
|
+
maximumFractionDigits: number;
|
|
38
|
+
/** Minimum fractional digits (0–20). Padded with trailing zeros.
|
|
39
|
+
Useful for currency display to force ".00" suffix.
|
|
40
|
+
*/
|
|
41
|
+
minimumFractionDigits: number;
|
|
42
|
+
/** `Intl.NumberFormat` `notation` option. `standard` is the
|
|
43
|
+
thousands-grouped form (1,234,567); `compact` is the abbreviated
|
|
44
|
+
form (1.2M); `scientific` and `engineering` are the exponent
|
|
45
|
+
forms. Defaults to `standard`.
|
|
46
|
+
*/
|
|
47
|
+
notation: 'standard' | 'compact' | 'scientific' | 'engineering';
|
|
48
|
+
/** `Intl.NumberFormat` `style` option. `decimal` (default) renders a
|
|
49
|
+
plain number with locale-aware grouping. `currency` requires
|
|
50
|
+
[currency] to be set. `percent` formats 0–1 as 0%–100%. `unit`
|
|
51
|
+
requires [unit] to be set (e.g. "kilobyte", "celsius").
|
|
52
|
+
*/
|
|
53
|
+
numberStyle: 'decimal' | 'currency' | 'percent' | 'unit';
|
|
54
|
+
/** `Intl.NumberFormat` `signDisplay` option. `auto` (default) shows
|
|
55
|
+
"−" for negatives only; `always` shows "+" / "−"; `exceptZero`
|
|
56
|
+
shows sign for non-zero only; `never` hides signs.
|
|
57
|
+
*/
|
|
58
|
+
signDisplay: 'auto' | 'always' | 'exceptZero' | 'never';
|
|
59
|
+
/** `Intl.NumberFormat` unit identifier (e.g. "kilometer-per-hour",
|
|
60
|
+
"celsius", "byte"). Required when [number-style="unit"]; ignored
|
|
61
|
+
otherwise. See MDN's NumberFormat docs for the valid set.
|
|
62
|
+
*/
|
|
63
|
+
unit: string;
|
|
64
|
+
/** The numeric value to format. Empty string or non-numeric value
|
|
65
|
+
renders nothing.
|
|
66
|
+
*/
|
|
67
|
+
value: number;
|
|
68
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<number-format-ui>` — auto-registers the tag on import.
|
|
3
|
+
*
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
6
|
+
*
|
|
7
|
+
* import { UINumberFormat } from '@adia-ai/web-components/components/number-format/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UINumberFormat } from './number-format.class.js';
|
|
14
|
+
|
|
15
|
+
defineIfFree('number-format-ui', UINumberFormat);
|
|
16
|
+
|
|
17
|
+
export { UINumberFormat };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
2
|
+
name: UINumberFormat
|
|
3
|
+
tag: number-format-ui
|
|
4
|
+
status: stable
|
|
5
|
+
component: NumberFormat
|
|
6
|
+
category: display
|
|
7
|
+
version: 1
|
|
8
|
+
description: |
|
|
9
|
+
Display a numeric value with locale-aware formatting — currency,
|
|
10
|
+
percentage, compact (1.2K / 3.4M), unit, or plain decimal. Wraps
|
|
11
|
+
`Intl.NumberFormat`. Distinct from `<input-ui type="number">`
|
|
12
|
+
(an INPUT primitive); this is a DISPLAY primitive — read-only, no
|
|
13
|
+
form participation, no keyboard handling. Pair with `<stat-ui>` for
|
|
14
|
+
KPI surfaces or use standalone inline within prose.
|
|
15
|
+
props:
|
|
16
|
+
value:
|
|
17
|
+
description: |
|
|
18
|
+
The numeric value to format. Empty string or non-numeric value
|
|
19
|
+
renders nothing.
|
|
20
|
+
type: number
|
|
21
|
+
default: 0
|
|
22
|
+
reflect: true
|
|
23
|
+
numberStyle:
|
|
24
|
+
description: |
|
|
25
|
+
`Intl.NumberFormat` `style` option. `decimal` (default) renders a
|
|
26
|
+
plain number with locale-aware grouping. `currency` requires
|
|
27
|
+
[currency] to be set. `percent` formats 0–1 as 0%–100%. `unit`
|
|
28
|
+
requires [unit] to be set (e.g. "kilobyte", "celsius").
|
|
29
|
+
type: string
|
|
30
|
+
default: decimal
|
|
31
|
+
enum: [decimal, currency, percent, unit]
|
|
32
|
+
reflect: true
|
|
33
|
+
attribute: number-style
|
|
34
|
+
currency:
|
|
35
|
+
description: |
|
|
36
|
+
ISO 4217 currency code (e.g. "USD", "EUR", "JPY"). Required when
|
|
37
|
+
[number-style="currency"]; ignored otherwise.
|
|
38
|
+
type: string
|
|
39
|
+
default: ""
|
|
40
|
+
reflect: true
|
|
41
|
+
unit:
|
|
42
|
+
description: |
|
|
43
|
+
`Intl.NumberFormat` unit identifier (e.g. "kilometer-per-hour",
|
|
44
|
+
"celsius", "byte"). Required when [number-style="unit"]; ignored
|
|
45
|
+
otherwise. See MDN's NumberFormat docs for the valid set.
|
|
46
|
+
type: string
|
|
47
|
+
default: ""
|
|
48
|
+
reflect: true
|
|
49
|
+
notation:
|
|
50
|
+
description: |
|
|
51
|
+
`Intl.NumberFormat` `notation` option. `standard` is the
|
|
52
|
+
thousands-grouped form (1,234,567); `compact` is the abbreviated
|
|
53
|
+
form (1.2M); `scientific` and `engineering` are the exponent
|
|
54
|
+
forms. Defaults to `standard`.
|
|
55
|
+
type: string
|
|
56
|
+
default: standard
|
|
57
|
+
enum: [standard, compact, scientific, engineering]
|
|
58
|
+
reflect: true
|
|
59
|
+
compactDisplay:
|
|
60
|
+
description: |
|
|
61
|
+
When [notation="compact"], controls the compact-form length.
|
|
62
|
+
`short` = "1.2M" (default); `long` = "1.2 million".
|
|
63
|
+
type: string
|
|
64
|
+
default: short
|
|
65
|
+
enum: [short, long]
|
|
66
|
+
reflect: true
|
|
67
|
+
attribute: compact-display
|
|
68
|
+
minimumFractionDigits:
|
|
69
|
+
description: |
|
|
70
|
+
Minimum fractional digits (0–20). Padded with trailing zeros.
|
|
71
|
+
Useful for currency display to force ".00" suffix.
|
|
72
|
+
type: number
|
|
73
|
+
default: 0
|
|
74
|
+
reflect: true
|
|
75
|
+
attribute: minimum-fraction-digits
|
|
76
|
+
maximumFractionDigits:
|
|
77
|
+
description: |
|
|
78
|
+
Maximum fractional digits (0–20). Default `2` for decimal/
|
|
79
|
+
currency/percent, `1` for compact notation.
|
|
80
|
+
type: number
|
|
81
|
+
default: 2
|
|
82
|
+
reflect: true
|
|
83
|
+
attribute: maximum-fraction-digits
|
|
84
|
+
locale:
|
|
85
|
+
description: |
|
|
86
|
+
BCP-47 locale tag for the formatter. Empty defaults to the
|
|
87
|
+
document locale (`<html lang>`) then to browser default.
|
|
88
|
+
type: string
|
|
89
|
+
default: ""
|
|
90
|
+
reflect: true
|
|
91
|
+
signDisplay:
|
|
92
|
+
description: |
|
|
93
|
+
`Intl.NumberFormat` `signDisplay` option. `auto` (default) shows
|
|
94
|
+
"−" for negatives only; `always` shows "+" / "−"; `exceptZero`
|
|
95
|
+
shows sign for non-zero only; `never` hides signs.
|
|
96
|
+
type: string
|
|
97
|
+
default: auto
|
|
98
|
+
enum: [auto, always, exceptZero, never]
|
|
99
|
+
reflect: true
|
|
100
|
+
attribute: sign-display
|
|
101
|
+
events: {}
|
|
102
|
+
slots: {}
|
|
103
|
+
states:
|
|
104
|
+
- name: idle
|
|
105
|
+
description: Default, displaying the formatted value.
|
|
106
|
+
traits: []
|
|
107
|
+
tokens: {}
|
|
108
|
+
a2ui:
|
|
109
|
+
rules:
|
|
110
|
+
- rule: "Use for read-only numeric display with locale-aware formatting. For numeric INPUT use <input-ui type=number>; for KPI big-number display use <stat-ui>."
|
|
111
|
+
reason: "Display vs input vs metric separation."
|
|
112
|
+
- rule: "[number-style=currency] REQUIRES [currency] to be set to a valid ISO 4217 code. Without it the element renders nothing rather than producing malformed output."
|
|
113
|
+
reason: "Currency contract."
|
|
114
|
+
- rule: "[number-style=percent] treats the [value] as a fraction (0.5 → 50%). To display 50 as '50%' pass value=0.5 OR keep [number-style=decimal] and append '%' manually."
|
|
115
|
+
reason: "Percent semantics — Intl.NumberFormat convention."
|
|
116
|
+
- rule: "Compact notation auto-defaults maximumFractionDigits to 1. Override only when extra precision is needed (rare for compact display — defeats the purpose)."
|
|
117
|
+
reason: "Sensible defaults."
|
|
118
|
+
anti_patterns:
|
|
119
|
+
- wrong: |
|
|
120
|
+
<number-format-ui value="50" number-style="percent"></number-format-ui>
|
|
121
|
+
why: |
|
|
122
|
+
Renders "5,000%" — Intl.NumberFormat percent style multiplies the
|
|
123
|
+
input by 100. value=50 means "5000%".
|
|
124
|
+
fix: |
|
|
125
|
+
<number-format-ui value="0.5" number-style="percent"></number-format-ui>
|
|
126
|
+
- wrong: |
|
|
127
|
+
<number-format-ui value="9.99" number-style="currency"></number-format-ui>
|
|
128
|
+
why: |
|
|
129
|
+
Missing [currency] code. Renders nothing rather than producing
|
|
130
|
+
malformed currency output.
|
|
131
|
+
fix: |
|
|
132
|
+
<number-format-ui value="9.99" number-style="currency" currency="USD"></number-format-ui>
|
|
133
|
+
examples:
|
|
134
|
+
- name: decimal
|
|
135
|
+
description: Plain locale-grouped number — default style.
|
|
136
|
+
a2ui: |
|
|
137
|
+
[
|
|
138
|
+
{
|
|
139
|
+
"id": "n",
|
|
140
|
+
"component": "NumberFormat",
|
|
141
|
+
"value": 1234567.89
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
- name: currency
|
|
145
|
+
description: USD currency display.
|
|
146
|
+
a2ui: |
|
|
147
|
+
[
|
|
148
|
+
{
|
|
149
|
+
"id": "n",
|
|
150
|
+
"component": "NumberFormat",
|
|
151
|
+
"value": 1299.99,
|
|
152
|
+
"numberStyle": "currency",
|
|
153
|
+
"currency": "USD"
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
- name: percent
|
|
157
|
+
description: Fraction → percent (0.876 → 87.6%).
|
|
158
|
+
a2ui: |
|
|
159
|
+
[
|
|
160
|
+
{
|
|
161
|
+
"id": "n",
|
|
162
|
+
"component": "NumberFormat",
|
|
163
|
+
"value": 0.876,
|
|
164
|
+
"numberStyle": "percent",
|
|
165
|
+
"maximumFractionDigits": 1
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
- name: compact
|
|
169
|
+
description: Compact-form (1.2M, 3.4K).
|
|
170
|
+
a2ui: |
|
|
171
|
+
[
|
|
172
|
+
{
|
|
173
|
+
"id": "n",
|
|
174
|
+
"component": "NumberFormat",
|
|
175
|
+
"value": 1234567,
|
|
176
|
+
"notation": "compact"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
keywords:
|
|
180
|
+
- number-format
|
|
181
|
+
- format
|
|
182
|
+
- currency
|
|
183
|
+
- percent
|
|
184
|
+
- percentage
|
|
185
|
+
- compact
|
|
186
|
+
- number
|
|
187
|
+
- locale
|
|
188
|
+
- intl
|
|
189
|
+
synonyms:
|
|
190
|
+
number:
|
|
191
|
+
- number-format
|
|
192
|
+
- format
|
|
193
|
+
currency:
|
|
194
|
+
- number-format
|
|
195
|
+
- money
|
|
196
|
+
- price
|
|
197
|
+
percent:
|
|
198
|
+
- number-format
|
|
199
|
+
- percentage
|
|
200
|
+
related:
|
|
201
|
+
- stat
|
|
202
|
+
- text
|
|
203
|
+
- badge
|
|
204
|
+
- input
|
|
@@ -26,14 +26,28 @@
|
|
|
26
26
|
"type": "number",
|
|
27
27
|
"default": 1
|
|
28
28
|
},
|
|
29
|
+
"size": {
|
|
30
|
+
"description": "Universal size — threads through to every nested `<button-ui size=…>`\nso pagination honors the substrate's 24/30/36 px size system\n(with [density] modifier). Default `md` matches `<button-ui>`'s\ndefault; pass `size=\"sm\"` for a denser numbered row.\n",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": [
|
|
33
|
+
"sm",
|
|
34
|
+
"md",
|
|
35
|
+
"lg"
|
|
36
|
+
],
|
|
37
|
+
"default": "md"
|
|
38
|
+
},
|
|
29
39
|
"total": {
|
|
30
40
|
"description": "Total number of pages.",
|
|
31
41
|
"type": "number",
|
|
32
42
|
"default": 1
|
|
33
43
|
},
|
|
34
44
|
"variant": {
|
|
35
|
-
"description": "Visual variant",
|
|
45
|
+
"description": "Visual variant — `default` (ghost buttons w/ hover bg) or `button` (1×1 bordered cells; active page filled).",
|
|
36
46
|
"type": "string",
|
|
47
|
+
"enum": [
|
|
48
|
+
"default",
|
|
49
|
+
"button"
|
|
50
|
+
],
|
|
37
51
|
"default": "default"
|
|
38
52
|
}
|
|
39
53
|
},
|
|
@@ -44,7 +58,10 @@
|
|
|
44
58
|
"x-adiaui": {
|
|
45
59
|
"anti_patterns": [],
|
|
46
60
|
"category": "navigation",
|
|
47
|
-
"composes": [
|
|
61
|
+
"composes": [
|
|
62
|
+
"button-ui",
|
|
63
|
+
"icon-ui"
|
|
64
|
+
],
|
|
48
65
|
"events": {
|
|
49
66
|
"page-change": {
|
|
50
67
|
"description": "Fired when a page button is clicked. detail contains { page }.",
|
|
@@ -29,8 +29,18 @@ export class UIPagination extends UIElement {
|
|
|
29
29
|
total: { type: Number, default: 1, reflect: true },
|
|
30
30
|
siblings: { type: Number, default: 1, reflect: true },
|
|
31
31
|
variant: { type: String, default: 'default', reflect: true },
|
|
32
|
+
// Threads through to every nested <button-ui size=…> so pagination
|
|
33
|
+
// honors the universal [size] system (sm=24 / md=30 / lg=36 px with
|
|
34
|
+
// density modifier). Default `md` matches <button-ui>'s default —
|
|
35
|
+
// pagination is a button-ui composite, so the canonical size is the
|
|
36
|
+
// same. Authors who want a denser numbered row pass [size="sm"].
|
|
37
|
+
size: { type: String, default: 'md', reflect: true },
|
|
32
38
|
};
|
|
33
39
|
|
|
40
|
+
// Phosphor icons stamped by this primitive (prev/next chevrons inside
|
|
41
|
+
// the nested <button-ui>). Audited by check-required-icons.mjs.
|
|
42
|
+
static requiredIcons = ['caret-left', 'caret-right'];
|
|
43
|
+
|
|
34
44
|
static template = () => null;
|
|
35
45
|
|
|
36
46
|
#nav = null;
|
|
@@ -46,12 +56,15 @@ export class UIPagination extends UIElement {
|
|
|
46
56
|
|
|
47
57
|
if (!this.#bound) {
|
|
48
58
|
this.#bound = true;
|
|
49
|
-
|
|
59
|
+
// `press` is the canonical button-ui event — fires only when not
|
|
60
|
+
// disabled (button-ui stops native click propagation on disabled
|
|
61
|
+
// state), so we get the right gating for free.
|
|
62
|
+
this.#nav.addEventListener('press', this.#onPress);
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
disconnected() {
|
|
54
|
-
this.#nav?.removeEventListener('
|
|
67
|
+
this.#nav?.removeEventListener('press', this.#onPress);
|
|
55
68
|
this.#nav = null;
|
|
56
69
|
this.#bound = false;
|
|
57
70
|
}
|
|
@@ -81,42 +94,70 @@ export class UIPagination extends UIElement {
|
|
|
81
94
|
|
|
82
95
|
#buildRange(page, total, siblings) {
|
|
83
96
|
const items = [];
|
|
84
|
-
|
|
85
|
-
// Prev button
|
|
86
97
|
items.push({ key: 'prev', type: 'prev', value: page - 1 });
|
|
87
98
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
// W = constant compact-mode width (visible page cells, incl ellipses).
|
|
100
|
+
// = 2 bookends + (2*siblings + 1 sibling window) + 2 ellipsis slots
|
|
101
|
+
// siblings=1 → W=7; siblings=2 → W=9.
|
|
102
|
+
// Holding W invariant across page positions prevents the layout from
|
|
103
|
+
// jumping when the current page advances by one — fixes the wobble
|
|
104
|
+
// where page=3 showed 6 cells but page=4 showed 7.
|
|
105
|
+
const W = 2 * siblings + 5;
|
|
106
|
+
|
|
107
|
+
if (total <= W) {
|
|
108
|
+
// Small total — show every page, never ellipsis. No need to compact
|
|
109
|
+
// when compacting wouldn't save horizontal slots.
|
|
110
|
+
for (let i = 1; i <= total; i++) {
|
|
111
|
+
items.push({ key: `page-${i}`, type: 'page', value: i });
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// Compact mode. Three layouts, each yielding exactly W cells so the
|
|
115
|
+
// row width stays constant under any page advance.
|
|
116
|
+
const nearStart = page <= siblings + 3;
|
|
117
|
+
const nearEnd = page >= total - siblings - 2;
|
|
118
|
+
|
|
119
|
+
if (nearStart) {
|
|
120
|
+
// 1..(W-2), ellipsis, total. The left window expands rightward to
|
|
121
|
+
// fill the slot a left ellipsis would have used.
|
|
122
|
+
const leftEnd = W - 2;
|
|
123
|
+
for (let i = 1; i <= leftEnd; i++) {
|
|
124
|
+
items.push({ key: `page-${i}`, type: 'page', value: i });
|
|
125
|
+
}
|
|
126
|
+
items.push({ key: 'ellipsis-end', type: 'ellipsis' });
|
|
127
|
+
items.push({ key: `page-${total}`, type: 'page', value: total });
|
|
128
|
+
} else if (nearEnd) {
|
|
129
|
+
// 1, ellipsis, (total-W+3)..total. Right window expands leftward.
|
|
130
|
+
items.push({ key: 'page-1', type: 'page', value: 1 });
|
|
131
|
+
items.push({ key: 'ellipsis-start', type: 'ellipsis' });
|
|
132
|
+
const rightStart = total - W + 3;
|
|
133
|
+
for (let i = rightStart; i <= total; i++) {
|
|
134
|
+
items.push({ key: `page-${i}`, type: 'page', value: i });
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Middle. 1, ellipsis, current±siblings, ellipsis, total.
|
|
138
|
+
items.push({ key: 'page-1', type: 'page', value: 1 });
|
|
139
|
+
items.push({ key: 'ellipsis-start', type: 'ellipsis' });
|
|
140
|
+
for (let i = page - siblings; i <= page + siblings; i++) {
|
|
141
|
+
items.push({ key: `page-${i}`, type: 'page', value: i });
|
|
142
|
+
}
|
|
143
|
+
items.push({ key: 'ellipsis-end', type: 'ellipsis' });
|
|
144
|
+
items.push({ key: `page-${total}`, type: 'page', value: total });
|
|
145
|
+
}
|
|
112
146
|
}
|
|
113
147
|
|
|
114
|
-
// Next button
|
|
115
148
|
items.push({ key: 'next', type: 'next', value: page + 1 });
|
|
116
|
-
|
|
117
149
|
return items;
|
|
118
150
|
}
|
|
119
151
|
|
|
152
|
+
// The non-active button variant \u2014 `ghost` for default mode (chrome-less
|
|
153
|
+
// hover), `outline` for variant="button" mode (1\u00D71 bordered cells).
|
|
154
|
+
// Active items always use `primary` so the active-state token chain
|
|
155
|
+
// comes from button-ui's primary surface matrix (no re-implementing
|
|
156
|
+
// a separate accent fill at the pagination tier).
|
|
157
|
+
#restVariant() {
|
|
158
|
+
return this.variant === 'button' ? 'outline' : 'ghost';
|
|
159
|
+
}
|
|
160
|
+
|
|
120
161
|
#createItem(item, page) {
|
|
121
162
|
if (item.type === 'ellipsis') {
|
|
122
163
|
const span = document.createElement('span');
|
|
@@ -125,21 +166,21 @@ export class UIPagination extends UIElement {
|
|
|
125
166
|
return span;
|
|
126
167
|
}
|
|
127
168
|
|
|
128
|
-
const btn = document.createElement('button');
|
|
129
|
-
btn.setAttribute('
|
|
169
|
+
const btn = document.createElement('button-ui');
|
|
170
|
+
btn.setAttribute('size', this.size);
|
|
130
171
|
|
|
131
172
|
if (item.type === 'prev') {
|
|
132
173
|
btn.setAttribute('data-prev', '');
|
|
174
|
+
btn.setAttribute('icon', 'caret-left');
|
|
133
175
|
btn.setAttribute('aria-label', 'Previous page');
|
|
134
|
-
btn.textContent = '\u2039';
|
|
135
176
|
} else if (item.type === 'next') {
|
|
136
177
|
btn.setAttribute('data-next', '');
|
|
178
|
+
btn.setAttribute('icon', 'caret-right');
|
|
137
179
|
btn.setAttribute('aria-label', 'Next page');
|
|
138
|
-
btn.textContent = '\u203A';
|
|
139
180
|
} else {
|
|
140
181
|
btn.setAttribute('data-page', '');
|
|
141
182
|
btn.dataset.value = String(item.value);
|
|
142
|
-
btn.
|
|
183
|
+
btn.setAttribute('text', String(item.value));
|
|
143
184
|
btn.setAttribute('aria-label', `Page ${item.value}`);
|
|
144
185
|
}
|
|
145
186
|
|
|
@@ -150,27 +191,39 @@ export class UIPagination extends UIElement {
|
|
|
150
191
|
#updateItem(el, item, page) {
|
|
151
192
|
if (item.type === 'ellipsis') return;
|
|
152
193
|
|
|
194
|
+
// Keep size in sync \u2014 the host's [size] may change between renders.
|
|
195
|
+
el.setAttribute('size', this.size);
|
|
196
|
+
|
|
153
197
|
if (item.type === 'prev') {
|
|
198
|
+
el.setAttribute('variant', this.#restVariant());
|
|
154
199
|
if (page <= 1) { el.setAttribute('disabled', ''); el.setAttribute('tabindex', '-1'); }
|
|
155
200
|
else { el.removeAttribute('disabled'); el.setAttribute('tabindex', '0'); }
|
|
156
201
|
} else if (item.type === 'next') {
|
|
202
|
+
el.setAttribute('variant', this.#restVariant());
|
|
157
203
|
if (page >= this.total) { el.setAttribute('disabled', ''); el.setAttribute('tabindex', '-1'); }
|
|
158
204
|
else { el.removeAttribute('disabled'); el.setAttribute('tabindex', '0'); }
|
|
159
205
|
} else {
|
|
160
206
|
el.dataset.value = String(item.value);
|
|
161
|
-
el.
|
|
207
|
+
el.setAttribute('text', String(item.value));
|
|
162
208
|
el.setAttribute('aria-label', `Page ${item.value}`);
|
|
163
209
|
if (item.value === page) {
|
|
210
|
+
// Active page reads as `variant="primary"` so the filled-accent
|
|
211
|
+
// state comes from button-ui's primary surface matrix (the
|
|
212
|
+
// canonical token chain) \u2014 not a pagination-tier re-impl.
|
|
213
|
+
el.setAttribute('variant', 'primary');
|
|
164
214
|
el.setAttribute('data-active', '');
|
|
165
215
|
el.setAttribute('aria-current', 'page');
|
|
166
216
|
} else {
|
|
217
|
+
el.setAttribute('variant', this.#restVariant());
|
|
167
218
|
el.removeAttribute('data-active');
|
|
168
219
|
el.removeAttribute('aria-current');
|
|
169
220
|
}
|
|
170
221
|
}
|
|
171
222
|
}
|
|
172
223
|
|
|
173
|
-
#
|
|
224
|
+
#onPress = (e) => {
|
|
225
|
+
// `press` fires on the <button-ui> itself, which is exactly the
|
|
226
|
+
// element carrying the data-prev / data-next / data-page marker.
|
|
174
227
|
const btn = e.target.closest('[data-prev], [data-next], [data-page]');
|
|
175
228
|
if (!btn || btn.hasAttribute('disabled') || !this.#nav.contains(btn)) return;
|
|
176
229
|
|