@antadesign/anta 0.3.2 → 0.3.3

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 (92) hide show
  1. package/dist/anta_helpers.d.ts +101 -0
  2. package/dist/anta_helpers.js +77 -0
  3. package/dist/calendar-core.d.ts +126 -0
  4. package/dist/calendar-core.js +289 -0
  5. package/dist/components/Button.d.ts +9 -1
  6. package/dist/components/Button.js +5 -2
  7. package/dist/components/Calendar.d.ts +80 -0
  8. package/dist/components/Calendar.js +220 -0
  9. package/dist/components/Checkbox.d.ts +16 -11
  10. package/dist/components/Checkbox.js +13 -7
  11. package/dist/components/Expander.d.ts +7 -1
  12. package/dist/components/Expander.js +4 -2
  13. package/dist/components/Input.d.ts +13 -5
  14. package/dist/components/Input.js +14 -6
  15. package/dist/components/InputDate.d.ts +87 -0
  16. package/dist/components/InputDate.js +278 -0
  17. package/dist/components/InputDate.module.css +1 -0
  18. package/dist/components/Menu.d.ts +5 -1
  19. package/dist/components/Menu.js +6 -2
  20. package/dist/components/MenuItem.d.ts +48 -7
  21. package/dist/components/MenuItem.js +71 -7
  22. package/dist/components/MenuSeparator.d.ts +13 -2
  23. package/dist/components/MenuSeparator.js +12 -2
  24. package/dist/components/Progress.d.ts +4 -1
  25. package/dist/components/Progress.js +4 -3
  26. package/dist/components/RadioGroup.d.ts +12 -10
  27. package/dist/components/RadioGroup.js +5 -5
  28. package/dist/components/Select.d.ts +255 -0
  29. package/dist/components/Select.js +290 -0
  30. package/dist/components/Select.module.css +1 -0
  31. package/dist/components/Tab.d.ts +3 -0
  32. package/dist/components/Tab.js +2 -1
  33. package/dist/components/TabPanel.js +2 -1
  34. package/dist/components/Tabs.d.ts +18 -1
  35. package/dist/components/Tabs.js +8 -6
  36. package/dist/components/Tag.d.ts +10 -9
  37. package/dist/components/Tag.js +2 -2
  38. package/dist/components/Text.d.ts +6 -5
  39. package/dist/components/Title.d.ts +4 -2
  40. package/dist/components/Tooltip.d.ts +4 -1
  41. package/dist/components/Tooltip.js +5 -0
  42. package/dist/elements/a-button.css +1 -1
  43. package/dist/elements/a-calendar.css +1 -0
  44. package/dist/elements/a-calendar.d.ts +76 -0
  45. package/dist/elements/a-calendar.js +190 -0
  46. package/dist/elements/a-checkbox.css +1 -1
  47. package/dist/elements/a-checkbox.d.ts +1 -2
  48. package/dist/elements/a-checkbox.js +5 -5
  49. package/dist/elements/a-expander.css +1 -1
  50. package/dist/elements/a-expander.d.ts +16 -5
  51. package/dist/elements/a-expander.js +59 -8
  52. package/dist/elements/a-icon.shapes.css +1 -1
  53. package/dist/elements/a-icon.shapes.d.ts +6 -1
  54. package/dist/elements/a-icon.shapes.js +9 -0
  55. package/dist/elements/a-input.css +1 -1
  56. package/dist/elements/a-input.d.ts +6 -0
  57. package/dist/elements/a-input.js +27 -8
  58. package/dist/elements/a-menu-group.css +1 -1
  59. package/dist/elements/a-menu-item.css +1 -1
  60. package/dist/elements/a-menu-item.d.ts +14 -4
  61. package/dist/elements/a-menu-item.js +17 -0
  62. package/dist/elements/a-menu-separator.css +1 -1
  63. package/dist/elements/a-menu.css +1 -1
  64. package/dist/elements/a-menu.d.ts +51 -8
  65. package/dist/elements/a-menu.js +285 -42
  66. package/dist/elements/a-progress.css +1 -1
  67. package/dist/elements/a-radio-group.d.ts +1 -3
  68. package/dist/elements/a-radio-group.js +13 -13
  69. package/dist/elements/a-radio.css +1 -1
  70. package/dist/elements/a-radio.d.ts +3 -11
  71. package/dist/elements/a-radio.js +3 -34
  72. package/dist/elements/a-tab.css +1 -1
  73. package/dist/elements/a-tab.d.ts +3 -11
  74. package/dist/elements/a-tab.js +3 -34
  75. package/dist/elements/a-tabs.css +1 -1
  76. package/dist/elements/a-tabs.d.ts +1 -4
  77. package/dist/elements/a-tabs.js +14 -14
  78. package/dist/elements/a-tag.css +1 -1
  79. package/dist/elements/a-text.d.ts +14 -13
  80. package/dist/elements/a-text.js +53 -23
  81. package/dist/elements/a-tooltip.css +1 -1
  82. package/dist/elements/a-tooltip.d.ts +1 -11
  83. package/dist/elements/a-tooltip.js +20 -19
  84. package/dist/elements/index.d.ts +1 -0
  85. package/dist/elements/index.js +3 -0
  86. package/dist/general_types.d.ts +127 -31
  87. package/dist/index.d.ts +9 -1
  88. package/dist/index.js +37 -1
  89. package/dist/jsx-runtime.d.ts +16 -7
  90. package/dist/jsx-runtime.js +6 -0
  91. package/dist/reset.css +1 -1
  92. package/package.json +4 -2
@@ -22,6 +22,10 @@ export declare function nativeStateChange<D>(e: CustomEvent<D> | {
22
22
  event: CustomEvent<D>;
23
23
  detail?: D;
24
24
  };
25
+ export declare const TABS_KIND: unique symbol;
26
+ export declare function markTabsKind<F>(component: F & {
27
+ [TABS_KIND]?: "tab" | "panel";
28
+ }, kind: "tab" | "panel"): F;
25
29
  /** The six named tones every toned component shares. Anything else is a literal
26
30
  * CSS colour the element resolves through its `--{component}-tone-source` var. */
27
31
  export declare const NAMED_TONES: Set<string>;
@@ -31,6 +35,52 @@ export declare const NAMED_TONES: Set<string>;
31
35
  * derives the fill/text/border curve in oklch. Named tones return `base` unchanged.
32
36
  */
33
37
  export declare function toneStyle(tone: string | undefined, varName: string, base?: React.CSSProperties): React.CSSProperties | undefined;
38
+ /**
39
+ * Inline-style helper for a valued `round` — the polymorphic corner-radius prop
40
+ * (`true` = fully round, a `number`/length = a custom radius). Mirrors `toneStyle`:
41
+ * a custom value is handed to the element via `varName` (e.g. `--button-round`), so
42
+ * the element's CSS resolves `border-radius: var(--{c}-round, <default>)`. This is
43
+ * the portable path — the element's `attr(round type(<length>), …)` default only
44
+ * resolves on newer engines, exactly like the tone-source `attr()`.
45
+ *
46
+ * A `number` becomes `<n>px`; a non-empty `string` is used verbatim (so `'1rem'` /
47
+ * `'50%'` work). `true` / `false` / `undefined` add nothing (presence alone → the
48
+ * element's default full-round). Returns `base` unchanged when there's no value.
49
+ */
50
+ export declare function roundStyle(round: boolean | number | string | undefined, varName: string, base?: React.CSSProperties): React.CSSProperties | undefined;
51
+ /**
52
+ * The rect a floating-element anchor advertises for positioning. Any element may
53
+ * implement `getAnchorRect(): DOMRect` to point positioners (`a-menu`,
54
+ * `a-tooltip`) at a sub-region of itself — e.g. `a-input` returns its `.field`
55
+ * box rather than the host, whose box also spans the label / hint, so a menu or
56
+ * tooltip lines up with the field itself. Elements without it fall back to their
57
+ * border box (`getBoundingClientRect`), so this is opt-in and back-compatible.
58
+ * Shared by a-menu and a-tooltip.
59
+ */
60
+ export declare function anchorRect(el: Element): DOMRect;
61
+ /**
62
+ * Menu ↔ Tooltip presence coordinator — in-memory only, no DOM. `a-menu`
63
+ * publishes whether a menu system is open and whether a node sits inside it;
64
+ * `a-tooltip` reads this to suppress a tooltip anchored *outside* an open menu.
65
+ * (Menu and tooltip are both top-layer popovers, stacked by show order, so a
66
+ * tooltip opened while a menu is up would paint over it — z-index can't reorder
67
+ * the top layer.) Decoupled on purpose: neither element imports the other, and
68
+ * if `a-menu` never loads the provider stays null and every tooltip shows.
69
+ */
70
+ export interface MenuPresence {
71
+ /** Is any `a-menu` open? */
72
+ isOpen(): boolean;
73
+ /** Is `node` inside the open menu system — a menu surface, a slotted item, or
74
+ * an open trigger? (A tooltip there is a menu-item tooltip and still shows,
75
+ * above the menu.) */
76
+ contains(node: Node): boolean;
77
+ }
78
+ /** `a-menu` registers its live open-state provider here (once, on load). */
79
+ export declare function setMenuPresence(p: MenuPresence | null): void;
80
+ /** True while any `a-menu` is open. */
81
+ export declare function isMenuOpen(): boolean;
82
+ /** True if `node` sits inside the open menu system. */
83
+ export declare function isInsideOpenMenu(node: Node): boolean;
34
84
  /**
35
85
  * `HTMLElement` in browsers, a noop class in Node/Worker environments.
36
86
  * Use this as the base for custom element classes so importing the
@@ -54,4 +104,55 @@ export declare class HTMLElementBase extends NativeHTMLElement {
54
104
  /** This element's own document (the iframe's document when nested). */
55
105
  protected get doc(): Document;
56
106
  }
107
+ /**
108
+ * Base for a coordinated presentational child — one option in a control whose
109
+ * parent owns the selection (`<a-radio>` in `<a-radio-group>`, `<a-tab>` in
110
+ * `<a-tabs>`). The parent is the single source of truth and pushes selection down
111
+ * by setting the `selected` *property* (never an attribute — that would mutate the
112
+ * child's DOM, which app-DOM-in-a-worker forbids). This child's only job is to
113
+ * reflect that into a `:state(selected)` custom state (the CSS hook) and one ARIA
114
+ * property — `aria-checked` for radios, `aria-selected` for tabs — through its OWN
115
+ * ElementInternals. It owns no selection *logic*: the `internals` bit is a render
116
+ * latch, not authoritative state.
117
+ *
118
+ * The `selected` attribute is a hand-author / raw-assembly seed for the initial
119
+ * paint only. `connectedCallback` reads it **ON-only, never OFF**: under eager
120
+ * element registration the parent connects first (parent-before-child tree order)
121
+ * and has already set the property by the time this runs, so forcing OFF for an
122
+ * absent attribute would silently clobber that initial selection (state right,
123
+ * nothing painted). The resting latch is already off, so seeding only-ON loses
124
+ * nothing. After connect, both the property setter and `attributeChangedCallback`
125
+ * drive selection two-way. Encoding the guard here (not per element) is what makes
126
+ * the clobber bug impossible by construction for every child that extends this.
127
+ *
128
+ * Subclasses set `ariaProp` to the ARIA reflection property their role needs.
129
+ */
130
+ export declare class SelectableChildElement extends HTMLElementBase {
131
+ static observedAttributes: string[];
132
+ /** ARIA property this element reflects selection into. Subclass sets it. */
133
+ protected ariaProp: 'ariaChecked' | 'ariaSelected';
134
+ private internals?;
135
+ connectedCallback(): void;
136
+ attributeChangedCallback(name: string): void;
137
+ get selected(): boolean;
138
+ /**
139
+ * Live selection, held off the DOM as `:state(selected)` (mirrored to `ariaProp`).
140
+ * The parent `a-radio-group` / `a-tabs` drives it by assigning the *property* a real
141
+ * boolean from its `sync()` (`el.selected = r === selectedEl`), so a genuine boolean
142
+ * is the only input the setter sees in practice.
143
+ *
144
+ * `!!on` is deliberate and matches native IDL booleans: `el.selected = ''` yields
145
+ * `false`, exactly like `input.disabled = '' → false`. Empty string is a falsy
146
+ * *value*; "presence means true" is an attribute-world rule (it governs `hasAttribute`
147
+ * in `attributeChangedCallback` and CSS `[selected]` matching) and does not carry to
148
+ * property assignment. React 19 assigns element props as properties, so a hand-written
149
+ * `<a-radio selected="">` there reads as `false` while the same markup in plain HTML
150
+ * reads as `true` — the split every boolean IDL property has. Write bare `selected` /
151
+ * `selected={true}` for on; both paths agree. Do not "fix" `!!on` to treat `''` as true.
152
+ */
153
+ set selected(on: boolean);
154
+ get value(): string;
155
+ set value(v: string);
156
+ protected applyState(on: boolean): void;
157
+ }
57
158
  export {};
@@ -16,6 +16,11 @@ function nativeStateChange(e) {
16
16
  const event = "nativeEvent" in e ? e.nativeEvent : e;
17
17
  return { event, detail: event?.detail };
18
18
  }
19
+ const TABS_KIND = Symbol.for("@antadesign/anta/tabs.kind");
20
+ function markTabsKind(component, kind) {
21
+ component[TABS_KIND] = kind;
22
+ return component;
23
+ }
19
24
  const NAMED_TONES = /* @__PURE__ */ new Set([
20
25
  "brand",
21
26
  "neutral",
@@ -27,6 +32,25 @@ const NAMED_TONES = /* @__PURE__ */ new Set([
27
32
  function toneStyle(tone, varName, base) {
28
33
  return tone != null && !NAMED_TONES.has(tone) ? { ...base, [varName]: tone } : base;
29
34
  }
35
+ function roundStyle(round, varName, base) {
36
+ if (typeof round === "number") return { ...base, [varName]: `${round}px` };
37
+ if (typeof round === "string" && round !== "") return { ...base, [varName]: round };
38
+ return base;
39
+ }
40
+ function anchorRect(el) {
41
+ const fn = el.getAnchorRect;
42
+ return typeof fn === "function" ? fn.call(el) : el.getBoundingClientRect();
43
+ }
44
+ let menuPresence = null;
45
+ function setMenuPresence(p) {
46
+ menuPresence = p;
47
+ }
48
+ function isMenuOpen() {
49
+ return menuPresence?.isOpen() ?? false;
50
+ }
51
+ function isInsideOpenMenu(node) {
52
+ return menuPresence?.contains(node) ?? false;
53
+ }
30
54
  const NativeHTMLElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
31
55
  };
32
56
  class HTMLElementBase extends NativeHTMLElement {
@@ -39,11 +63,64 @@ class HTMLElementBase extends NativeHTMLElement {
39
63
  return this.ownerDocument ?? document;
40
64
  }
41
65
  }
66
+ class SelectableChildElement extends HTMLElementBase {
67
+ static observedAttributes = ["selected"];
68
+ /** ARIA property this element reflects selection into. Subclass sets it. */
69
+ ariaProp = "ariaChecked";
70
+ internals = this.attachInternals?.();
71
+ connectedCallback() {
72
+ if (this.hasAttribute("selected")) this.applyState(true);
73
+ }
74
+ attributeChangedCallback(name) {
75
+ if (name === "selected") this.applyState(this.hasAttribute("selected"));
76
+ }
77
+ get selected() {
78
+ return this.internals?.states.has("selected") ?? false;
79
+ }
80
+ /**
81
+ * Live selection, held off the DOM as `:state(selected)` (mirrored to `ariaProp`).
82
+ * The parent `a-radio-group` / `a-tabs` drives it by assigning the *property* a real
83
+ * boolean from its `sync()` (`el.selected = r === selectedEl`), so a genuine boolean
84
+ * is the only input the setter sees in practice.
85
+ *
86
+ * `!!on` is deliberate and matches native IDL booleans: `el.selected = ''` yields
87
+ * `false`, exactly like `input.disabled = '' → false`. Empty string is a falsy
88
+ * *value*; "presence means true" is an attribute-world rule (it governs `hasAttribute`
89
+ * in `attributeChangedCallback` and CSS `[selected]` matching) and does not carry to
90
+ * property assignment. React 19 assigns element props as properties, so a hand-written
91
+ * `<a-radio selected="">` there reads as `false` while the same markup in plain HTML
92
+ * reads as `true` — the split every boolean IDL property has. Write bare `selected` /
93
+ * `selected={true}` for on; both paths agree. Do not "fix" `!!on` to treat `''` as true.
94
+ */
95
+ set selected(on) {
96
+ this.applyState(!!on);
97
+ }
98
+ get value() {
99
+ return this.getAttribute("value") ?? "";
100
+ }
101
+ set value(v) {
102
+ this.setAttribute("value", v);
103
+ }
104
+ applyState(on) {
105
+ if (!this.internals) return;
106
+ if (on) this.internals.states.add("selected");
107
+ else this.internals.states.delete("selected");
108
+ this.internals[this.ariaProp] = on ? "true" : "false";
109
+ }
110
+ }
42
111
  export {
43
112
  HTMLElementBase,
44
113
  NAMED_TONES,
114
+ SelectableChildElement,
115
+ TABS_KIND,
116
+ anchorRect,
45
117
  hasChildren,
118
+ isInsideOpenMenu,
119
+ isMenuOpen,
120
+ markTabsKind,
46
121
  nativeStateChange,
122
+ roundStyle,
123
+ setMenuPresence,
47
124
  toneStyle,
48
125
  wrapLabel
49
126
  };
@@ -0,0 +1,126 @@
1
+ import { Temporal } from 'temporal-polyfill';
2
+ /** One day cell in the rendered grid. */
3
+ export interface CalendarDay {
4
+ /** The date this cell represents. */
5
+ date: Temporal.PlainDate;
6
+ /** ISO `YYYY-MM-DD`. */
7
+ iso: string;
8
+ /** Day of month, 1–31. */
9
+ day: number;
10
+ /** Day of week, 1 (Mon) – 7 (Sun), matching Temporal. */
11
+ weekday: number;
12
+ /** True when the day belongs to an adjacent month (grid spill-over). */
13
+ outside: boolean;
14
+ /** True when the day is outside `[min, max]` (not selectable). */
15
+ disabled: boolean;
16
+ /** True when the day is the current date. */
17
+ today: boolean;
18
+ /** True when the day is the selected date. */
19
+ selected: boolean;
20
+ /** Localized full-date label incl. weekday, e.g. "Sunday, June 28, 2026"
21
+ * (for each day button's `aria-label`, since there's no column-header row). */
22
+ label: string;
23
+ }
24
+ /** A weekday column header, localized. */
25
+ export interface CalendarWeekday {
26
+ /** One-letter-ish narrow form (here trimmed to two: "Mo", "Tu", …). */
27
+ narrow: string;
28
+ /** Locale short form ("Mon", "lun.", …). */
29
+ short: string;
30
+ /** Locale long form ("Monday") — for the column header's accessible name. */
31
+ long: string;
32
+ /** Saturday or Sunday (locale-independent — by Temporal `dayOfWeek` 6/7). */
33
+ weekend: boolean;
34
+ }
35
+ /** A fully-computed month: heading, ordered weekday headers, and the week rows. */
36
+ export interface CalendarMonth {
37
+ /** Displayed year. */
38
+ year: number;
39
+ /** Displayed month, 1–12. */
40
+ month: number;
41
+ /** Localized "June 2026" heading. */
42
+ heading: string;
43
+ /** Seven weekday headers, ordered from the locale's first day of week. */
44
+ weekdays: CalendarWeekday[];
45
+ /** Week rows (6 by default), each seven `CalendarDay`s. */
46
+ weeks: CalendarDay[][];
47
+ }
48
+ export interface BuildMonthOptions {
49
+ /** Any date within the month to display. */
50
+ anchor: Temporal.PlainDate;
51
+ /** BCP-47 locale. @defaultValue 'en-US' */
52
+ locale?: string;
53
+ /** Earliest selectable date (inclusive). */
54
+ min?: Temporal.PlainDate | null;
55
+ /** Latest selectable date (inclusive). */
56
+ max?: Temporal.PlainDate | null;
57
+ /** The currently-selected date, if any. */
58
+ selected?: Temporal.PlainDate | null;
59
+ /** The date to mark as "today". @defaultValue Temporal.Now.plainDateISO() */
60
+ today?: Temporal.PlainDate | null;
61
+ /** Number of week rows. @defaultValue 6 */
62
+ weeks?: number;
63
+ }
64
+ /** Parse an ISO `YYYY-MM-DD` string to a `Temporal.PlainDate`, or `null` if
65
+ * it's empty or malformed. */
66
+ export declare function parseISODate(value: string | null | undefined): Temporal.PlainDate | null;
67
+ /** First weekday for a locale, `1`=Monday … `7`=Sunday (matches Temporal's
68
+ * `dayOfWeek`). Reads `Intl.Locale#getWeekInfo()` (or the `weekInfo` getter on
69
+ * engines that expose it that way); falls back to Monday — never hard-codes
70
+ * Sunday. */
71
+ export declare function firstDayOfWeek(locale: string): number;
72
+ /** Clamp a date into the inclusive `[min, max]` range. */
73
+ export declare function clampDate(d: Temporal.PlainDate, min?: Temporal.PlainDate | null, max?: Temporal.PlainDate | null): Temporal.PlainDate;
74
+ /** Whether a date falls outside the inclusive `[min, max]` range. */
75
+ export declare function isOutOfRange(d: Temporal.PlainDate, min?: Temporal.PlainDate | null, max?: Temporal.PlainDate | null): boolean;
76
+ /** The seven weekday headers, ordered from the locale's first day of week. */
77
+ export declare function getWeekdays(locale: string, firstDay?: number): CalendarWeekday[];
78
+ /** Build a fully-resolved month grid for rendering. */
79
+ export declare function buildMonth(opts: BuildMonthOptions): CalendarMonth;
80
+ /** The placeholder mask for a locale: `MM/DD/YYYY` (en-US), `DD.MM.YYYY` (de-DE),
81
+ * `YYYY/MM/DD` (ja-JP). Built from the locale's own separators and field order. */
82
+ export declare function dateFormatPattern(locale: string): string;
83
+ /** A date in the locale's canonical numeric form (2-digit month/day, 4-digit year,
84
+ * latin digits) — what the field shows once a typed or picked date resolves. */
85
+ export declare function formatDateInput(date: Temporal.PlainDate, locale: string): string;
86
+ /** Options for {@link parseDateInput}. */
87
+ export interface ParseDateOptions {
88
+ /** Earliest acceptable date; earlier input resolves to `null`. */
89
+ min?: Temporal.PlainDate | null;
90
+ /** Latest acceptable date; later input resolves to `null`. */
91
+ max?: Temporal.PlainDate | null;
92
+ }
93
+ /**
94
+ * Recognize a date from free-form text, or return `null` when it can't. Lenient
95
+ * by design (the field lets you type anything, then resolves on blur):
96
+ *
97
+ * - ISO `YYYY-MM-DD`.
98
+ * - Numbers in any separator, mapped to day/month/year by the locale's order —
99
+ * `06/07/2026` is June 7 in en-US, July 6 in de-DE.
100
+ * - A month name plus a day and year (`15 Jun 2026`, `June 15 2026`).
101
+ * - Eight run-together digits, split by the locale order (`06152026` en-US).
102
+ * - Two numbers (day + month) take the current year; two-digit years pivot at 70.
103
+ *
104
+ * A real but out-of-`[min, max]` date returns `null` — the value stays uncommitted.
105
+ */
106
+ export declare function parseDateInput(text: string, locale: string, opts?: ParseDateOptions): Temporal.PlainDate | null;
107
+ /** Recognize a wall-clock time from free-form text, or `null`. Accepts `14:30`,
108
+ * `9:5`, `230` / `1430` (run-together), and a 12-hour form with a meridiem
109
+ * (`2:30 pm`, `12am`), normalized to 24-hour. Minutes only (no seconds). */
110
+ export declare function parseTimeInput(text: string): Temporal.PlainTime | null;
111
+ /** Recognize a date-time from free-form text, or `null`. Peels a trailing time
112
+ * token that follows whitespace (`06/15/2026 14:30`, `June 15 2026 2:30pm`),
113
+ * parsing the rest as the date via {@link parseDateInput}; with no time token the
114
+ * time defaults to midnight. `min` / `max` bound the date (day-level). */
115
+ export declare function parseDateTimeInput(text: string, locale: string, opts?: ParseDateOptions): Temporal.PlainDateTime | null;
116
+ /** Whether a locale writes time in 12-hour form (AM/PM). `en-US` is `true`; most
117
+ * others (`en-GB`, `de-DE`, `ja-JP`, …) are `false`. Read from `Intl`'s resolved
118
+ * `hourCycle` (`h11`/`h12` → 12-hour). */
119
+ export declare function usesHour12(locale: string): boolean;
120
+ /** A date-time in the canonical display form: the locale's date plus the time in
121
+ * the given cycle — 24-hour `HH:mm` (`06/15/2026 14:30`) or 12-hour `hh:mm AM/PM`
122
+ * (`06/15/2026 02:30 PM`). `hour12` defaults to the locale's convention. */
123
+ export declare function formatDateTimeInput(dt: Temporal.PlainDateTime, locale: string, hour12?: boolean): string;
124
+ /** The placeholder mask for a date-time field: the date mask plus the time mask
125
+ * (`HH:MM`, or `HH:MM AM` in 12-hour locales). `hour12` defaults to the locale. */
126
+ export declare function dateTimeFormatPattern(locale: string, hour12?: boolean): string;
@@ -0,0 +1,289 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ const cmp = Temporal.PlainDate.compare;
3
+ const dtfCache = /* @__PURE__ */ new Map();
4
+ function dtf(locale, opts) {
5
+ const key = `${locale}|${JSON.stringify(opts)}`;
6
+ let f = dtfCache.get(key);
7
+ if (!f) {
8
+ f = new Intl.DateTimeFormat(locale, opts);
9
+ dtfCache.set(key, f);
10
+ }
11
+ return f;
12
+ }
13
+ const asUTCDate = (d) => new Date(Date.UTC(d.year, d.month - 1, d.day));
14
+ function parseISODate(value) {
15
+ if (!value) return null;
16
+ try {
17
+ return Temporal.PlainDate.from(value);
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function firstDayOfWeek(locale) {
23
+ try {
24
+ const loc = new Intl.Locale(locale);
25
+ const info = loc.getWeekInfo?.() ?? loc.weekInfo;
26
+ const fd = info?.firstDay;
27
+ if (typeof fd === "number" && fd >= 1 && fd <= 7) return fd;
28
+ } catch {
29
+ }
30
+ return 1;
31
+ }
32
+ function clampDate(d, min, max) {
33
+ if (min && cmp(d, min) < 0) return min;
34
+ if (max && cmp(d, max) > 0) return max;
35
+ return d;
36
+ }
37
+ function isOutOfRange(d, min, max) {
38
+ return min != null && cmp(d, min) < 0 || max != null && cmp(d, max) > 0;
39
+ }
40
+ const twoLetter = (s) => Array.from(s).slice(0, 2).join("");
41
+ function getWeekdays(locale, firstDay = firstDayOfWeek(locale)) {
42
+ const ref = Temporal.PlainDate.from("2021-02-01");
43
+ const start = ref.add({ days: (firstDay - ref.dayOfWeek + 7) % 7 });
44
+ const shortFmt = dtf(locale, { weekday: "short", timeZone: "UTC" });
45
+ const longFmt = dtf(locale, { weekday: "long", timeZone: "UTC" });
46
+ const out = [];
47
+ for (let i = 0; i < 7; i++) {
48
+ const d = start.add({ days: i });
49
+ const short = shortFmt.format(asUTCDate(d));
50
+ out.push({
51
+ short,
52
+ narrow: twoLetter(short),
53
+ long: longFmt.format(asUTCDate(d)),
54
+ weekend: d.dayOfWeek === 6 || d.dayOfWeek === 7
55
+ });
56
+ }
57
+ return out;
58
+ }
59
+ function buildMonth(opts) {
60
+ const {
61
+ anchor,
62
+ locale = "en-US",
63
+ min = null,
64
+ max = null,
65
+ selected = null,
66
+ today = Temporal.Now.plainDateISO(),
67
+ weeks = 6
68
+ } = opts;
69
+ const fd = firstDayOfWeek(locale);
70
+ const year = anchor.year;
71
+ const month = anchor.month;
72
+ const firstOfMonth = anchor.with({ day: 1 });
73
+ const lead = (firstOfMonth.dayOfWeek - fd + 7) % 7;
74
+ const start = firstOfMonth.subtract({ days: lead });
75
+ const labelFmt = dtf(locale, {
76
+ weekday: "long",
77
+ year: "numeric",
78
+ month: "long",
79
+ day: "numeric",
80
+ timeZone: "UTC"
81
+ });
82
+ const grid = [];
83
+ for (let w = 0; w < weeks; w++) {
84
+ const row = [];
85
+ for (let i = 0; i < 7; i++) {
86
+ const date = start.add({ days: w * 7 + i });
87
+ row.push({
88
+ date,
89
+ iso: date.toString(),
90
+ day: date.day,
91
+ weekday: date.dayOfWeek,
92
+ outside: date.month !== month || date.year !== year,
93
+ disabled: isOutOfRange(date, min, max),
94
+ today: today != null && cmp(date, today) === 0,
95
+ selected: selected != null && cmp(date, selected) === 0,
96
+ label: labelFmt.format(asUTCDate(date))
97
+ });
98
+ }
99
+ grid.push(row);
100
+ }
101
+ return {
102
+ year,
103
+ month,
104
+ heading: dtf(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(asUTCDate(anchor)),
105
+ weekdays: getWeekdays(locale, fd),
106
+ weeks: grid
107
+ };
108
+ }
109
+ const FMT_SAMPLE = new Date(Date.UTC(2026, 5, 15));
110
+ const NUMERIC_DATE = {
111
+ year: "numeric",
112
+ month: "2-digit",
113
+ day: "2-digit",
114
+ timeZone: "UTC",
115
+ numberingSystem: "latn"
116
+ };
117
+ function fieldOrder(locale) {
118
+ try {
119
+ return dtf(locale, NUMERIC_DATE).formatToParts(FMT_SAMPLE).filter((p) => p.type === "year" || p.type === "month" || p.type === "day").map((p) => p.type);
120
+ } catch {
121
+ return ["year", "month", "day"];
122
+ }
123
+ }
124
+ function dateFormatPattern(locale) {
125
+ try {
126
+ return dtf(locale, NUMERIC_DATE).formatToParts(FMT_SAMPLE).map(
127
+ (p) => p.type === "year" ? "YYYY" : p.type === "month" ? "MM" : p.type === "day" ? "DD" : p.value
128
+ ).join("");
129
+ } catch {
130
+ return "YYYY-MM-DD";
131
+ }
132
+ }
133
+ function formatDateInput(date, locale) {
134
+ return dtf(locale, NUMERIC_DATE).format(asUTCDate(date));
135
+ }
136
+ function matchMonthName(text, locale) {
137
+ const alpha = text.toLowerCase().match(/[^\d\s.,/-]+/g);
138
+ if (!alpha) return null;
139
+ const long = dtf(locale, { month: "long", timeZone: "UTC" });
140
+ const short = dtf(locale, { month: "short", timeZone: "UTC" });
141
+ for (let mo = 1; mo <= 12; mo++) {
142
+ const d = new Date(Date.UTC(2020, mo - 1, 1));
143
+ const l = long.format(d).toLowerCase();
144
+ const s = short.format(d).toLowerCase().replace(/\.$/, "");
145
+ if (alpha.some((a) => a === l || a === s || a.length >= 3 && l.startsWith(a))) return mo;
146
+ }
147
+ return null;
148
+ }
149
+ function parseDateInput(text, locale, opts = {}) {
150
+ const accept = (d2) => isOutOfRange(d2, opts.min ?? null, opts.max ?? null) ? null : d2;
151
+ const s = (text ?? "").trim();
152
+ if (!s) return null;
153
+ if (/^\d{4}-\d{2}-\d{2}$/.test(s)) {
154
+ const iso = parseISODate(s);
155
+ return iso ? accept(iso) : null;
156
+ }
157
+ const order = fieldOrder(locale);
158
+ const month = matchMonthName(s, locale);
159
+ const nums = (s.match(/\d+/g) ?? []).map(Number);
160
+ let y;
161
+ let m;
162
+ let d;
163
+ if (month != null) {
164
+ m = month;
165
+ if (nums.length === 1) {
166
+ d = nums[0];
167
+ y = Temporal.Now.plainDateISO().year;
168
+ } else if (nums.length >= 2) {
169
+ const [a, b] = nums;
170
+ if (a > 31) [y, d] = [a, b];
171
+ else if (b > 31) [y, d] = [b, a];
172
+ else [d, y] = [a, b];
173
+ } else return null;
174
+ } else if (nums.length === 1) {
175
+ const digits = s.replace(/\D/g, "");
176
+ if (digits.length !== 8) return null;
177
+ let i = 0;
178
+ const seg = {};
179
+ for (const f of order) {
180
+ const w = f === "year" ? 4 : 2;
181
+ seg[f] = Number(digits.slice(i, i + w));
182
+ i += w;
183
+ }
184
+ ;
185
+ ({ year: y, month: m, day: d } = seg);
186
+ } else if (nums.length === 2) {
187
+ const dm = order.filter((f) => f !== "year");
188
+ const seg = {};
189
+ dm.forEach((f, i) => seg[f] = nums[i]);
190
+ m = seg.month;
191
+ d = seg.day;
192
+ y = Temporal.Now.plainDateISO().year;
193
+ } else if (nums.length >= 3) {
194
+ const seg = {};
195
+ order.forEach((f, i) => seg[f] = nums[i]);
196
+ y = seg.year;
197
+ m = seg.month;
198
+ d = seg.day;
199
+ } else return null;
200
+ if (y != null && y < 100) y = y < 70 ? 2e3 + y : 1900 + y;
201
+ try {
202
+ return accept(Temporal.PlainDate.from({ year: y, month: m, day: d }, { overflow: "reject" }));
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ function parseTimeInput(text) {
208
+ let s = (text ?? "").trim().toLowerCase();
209
+ if (!s) return null;
210
+ let meridiem = null;
211
+ const mer = s.match(/([ap])\.?m?\.?$/);
212
+ if (mer) {
213
+ meridiem = mer[1] === "p" ? "pm" : "am";
214
+ s = s.slice(0, mer.index).trim();
215
+ }
216
+ let h;
217
+ let min;
218
+ if (/[:.]/.test(s)) {
219
+ const [hp, mp] = s.split(/[:.]/);
220
+ h = Number(hp);
221
+ min = Number(mp ?? "0");
222
+ } else {
223
+ const digits = s.replace(/\D/g, "");
224
+ if (!digits) return null;
225
+ if (digits.length <= 2) {
226
+ h = Number(digits);
227
+ min = 0;
228
+ } else {
229
+ const cut = digits.length - 2;
230
+ h = Number(digits.slice(0, cut));
231
+ min = Number(digits.slice(cut));
232
+ }
233
+ }
234
+ if (meridiem === "pm" && h < 12) h += 12;
235
+ if (meridiem === "am" && h === 12) h = 0;
236
+ try {
237
+ return Temporal.PlainTime.from({ hour: h, minute: min }, { overflow: "reject" });
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function parseDateTimeInput(text, locale, opts = {}) {
243
+ const s = (text ?? "").trim();
244
+ if (!s) return null;
245
+ const tm = s.match(/\s+(\d{1,2}[:.]\d{1,2}(?:\s*[ap]\.?m?\.?)?|\d{1,2}\s*[ap]\.?m\.?)\s*$/i);
246
+ let dateStr = s;
247
+ let time = null;
248
+ if (tm) {
249
+ time = parseTimeInput(tm[1]);
250
+ if (time) dateStr = s.slice(0, tm.index).trim();
251
+ }
252
+ const date = parseDateInput(dateStr, locale, opts);
253
+ if (!date) return null;
254
+ return date.toPlainDateTime(time ?? Temporal.PlainTime.from("00:00"));
255
+ }
256
+ function usesHour12(locale) {
257
+ try {
258
+ const hc = dtf(locale, { hour: "numeric" }).resolvedOptions().hourCycle;
259
+ return hc === "h11" || hc === "h12";
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+ function formatDateTimeInput(dt, locale, hour12 = usesHour12(locale)) {
265
+ const date = formatDateInput(dt.toPlainDate(), locale);
266
+ const mm = String(dt.minute).padStart(2, "0");
267
+ if (!hour12) return `${date} ${String(dt.hour).padStart(2, "0")}:${mm}`;
268
+ const h12 = (dt.hour + 11) % 12 + 1;
269
+ return `${date} ${String(h12).padStart(2, "0")}:${mm} ${dt.hour < 12 ? "AM" : "PM"}`;
270
+ }
271
+ function dateTimeFormatPattern(locale, hour12 = usesHour12(locale)) {
272
+ return `${dateFormatPattern(locale)} HH:MM${hour12 ? " AM" : ""}`;
273
+ }
274
+ export {
275
+ buildMonth,
276
+ clampDate,
277
+ dateFormatPattern,
278
+ dateTimeFormatPattern,
279
+ firstDayOfWeek,
280
+ formatDateInput,
281
+ formatDateTimeInput,
282
+ getWeekdays,
283
+ isOutOfRange,
284
+ parseDateInput,
285
+ parseDateTimeInput,
286
+ parseISODate,
287
+ parseTimeInput,
288
+ usesHour12
289
+ };
@@ -20,6 +20,10 @@ export type BaseButtonProps = {
20
20
  disabled?: boolean;
21
21
  /** Toggled-on / pressed state, e.g. for filter chips. */
22
22
  selected?: boolean;
23
+ /** Fully-round corners — a pill for text buttons, a circle for icon-only ones
24
+ * (`border-radius: 999px`, clamped to the element's height). Pass a `number`
25
+ * (px) or a CSS length string (`'1rem'`) for a custom radius instead. */
26
+ round?: boolean | number | string;
23
27
  /** Click handler. */
24
28
  onClick?: (e: any) => void;
25
29
  /** Tab order. The button is keyboard-focusable by default (`0`) and
@@ -29,6 +33,10 @@ export type BaseButtonProps = {
29
33
  * fire it mid-flight.
30
34
  * @defaultValue 0 */
31
35
  tabIndex?: number;
36
+ /** ARIA role override (e.g. `'gridcell'` when a button is a cell in a grid).
37
+ * Forwarded to the underlying element.
38
+ * @defaultValue button */
39
+ role?: string;
32
40
  };
33
41
  /** Content axis — slots render in this order inside the button:
34
42
  * `icon` → `label` → `children` → `iconTrailing`. Pass `icon` alone
@@ -113,4 +121,4 @@ export type ButtonProps = BaseButtonProps & PriorityMode & ContentMode & SubmitM
113
121
  * <Button href="/docs" target="_blank" label="Read the docs" />
114
122
  * ```
115
123
  */
116
- export declare const Button: ({ priority, tone, underline, icon, iconTrailing, paddingless, label, size, loading, disabled, selected, href, type, form, className, style, children, ...rest }: ButtonProps) => any;
124
+ export declare const Button: ({ priority, tone, underline, icon, iconTrailing, paddingless, label, size, loading, disabled, selected, round, href, type, form, className, style, children, ...rest }: ButtonProps) => any;