@antadesign/anta 0.3.31 → 0.3.33

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 (39) hide show
  1. package/dist/avatar-core.d.ts +7 -2
  2. package/dist/box-types.d.ts +12 -1
  3. package/dist/bundle.css +1 -1
  4. package/dist/bundle.js +148 -73
  5. package/dist/components/Box.d.ts +18 -6
  6. package/dist/components/Box.js +4 -0
  7. package/dist/components/Expander.d.ts +17 -9
  8. package/dist/components/Expander.js +14 -1
  9. package/dist/components/Input.d.ts +9 -3
  10. package/dist/components/Input.js +2 -0
  11. package/dist/components/Slider.d.ts +22 -10
  12. package/dist/components/Slider.js +9 -5
  13. package/dist/elements/a-box.css +1 -1
  14. package/dist/elements/a-box.d.ts +2 -0
  15. package/dist/elements/a-box.js +74 -32
  16. package/dist/elements/a-button.js +2 -4
  17. package/dist/elements/a-dialog.js +2 -0
  18. package/dist/elements/a-expander.css +1 -1
  19. package/dist/elements/a-expander.js +118 -39
  20. package/dist/elements/a-input-time.js +1 -2
  21. package/dist/elements/a-input.css +1 -1
  22. package/dist/elements/a-input.js +11 -8
  23. package/dist/elements/a-menu.d.ts +1 -1
  24. package/dist/elements/a-menu.js +57 -7
  25. package/dist/elements/a-slider.d.ts +7 -5
  26. package/dist/elements/a-slider.js +256 -59
  27. package/dist/general_types.d.ts +27 -10
  28. package/dist/index.d.ts +2 -2
  29. package/dist/theme-antune.css +1 -1
  30. package/docs/changelog.md +59 -0
  31. package/docs/components/box.md +81 -24
  32. package/docs/components/button.md +9 -6
  33. package/docs/components/expander.md +88 -47
  34. package/docs/components/input.md +29 -9
  35. package/docs/components/panel.md +3 -2
  36. package/docs/components/select-faceted.md +40 -26
  37. package/docs/components/slider.md +139 -4
  38. package/docs/packages/plot.md +392 -38
  39. package/package.json +4 -2
@@ -14,6 +14,13 @@ export interface BoxProps extends BaseProps {
14
14
  * pixels; a string is any CSS length or two-value gap (`'1rem'`,
15
15
  * `'8px 16px'`). Applies while the Box is a flex or grid container. */
16
16
  gap?: number | string;
17
+ /** CSS selector for descendants whose border boxes are included in
18
+ * `measurement.rects` when Box measures. Coordinates are relative to this
19
+ * Box's top-left border edge; matches are in document order. Box reads and
20
+ * compares matching rects for each measurement event. With `observe="scroll"`,
21
+ * use `throttle` when the selector matches many descendants; it also delays
22
+ * CSS state updates after the initial read. */
23
+ includeRectsFor?: string;
17
24
  /** Inner spacing, matching CSS `padding`. Numbers are pixels; strings accept
18
25
  * CSS shorthand, percentages, and custom properties. Omission adds no style. */
19
26
  padding?: number | string;
@@ -31,9 +38,13 @@ export interface BoxProps extends BaseProps {
31
38
  * overflow adds content observation; hidden edges and scroll add scroll reads.
32
39
  * Without handlers or `fade`, omission stays idle. */
33
40
  observe?: BoxObservation | readonly BoxObservation[];
34
- /** Minimum interval between measurement events, in milliseconds. The first
35
- * report has no added delay; a trailing report delivers the latest values.
36
- * Active observers and CSS clipping states are not throttled.
41
+ /** Keeps measurement events and CSS clipping states current outside the
42
+ * viewport. Has no effect unless `observe` or `fade` selects measurement. */
43
+ observeOffscreen?: boolean;
44
+ /** Minimum interval between measurements, in milliseconds. The initial
45
+ * measurement runs immediately; a trailing read updates CSS states and
46
+ * delivers the latest event. Descendant rects are read only for emitted
47
+ * events. The `measurement` getter always reads fresh values.
37
48
  * @defaultValue 0 */
38
49
  throttle?: number;
39
50
  /** Fades out every edge that currently hides clipped content, and drops the
@@ -43,8 +54,9 @@ export interface BoxProps extends BaseProps {
43
54
  * length.
44
55
  * @defaultValue 24 */
45
56
  fadeSize?: number | string;
46
- /** Fired when a selected measurement field changes. `detail` contains all
47
- * fields changed since the last event and a full current snapshot. */
57
+ /** Fired when a selected measurement field changes. `detail.changed` contains
58
+ * fields changed since the previous event; `detail.current` includes the full
59
+ * snapshot with matching rects. Rect changes alone do not trigger an event. */
48
60
  onMeasureChange?: (event: CustomEvent<BoxMeasurementChange>, detail: BoxMeasurementChange) => void;
49
61
  /** Fired after Box's browser and local rendering context changes. `detail`
50
62
  * contains the changed fields and a full current snapshot. */
@@ -65,4 +77,4 @@ export interface BoxProps extends BaseProps {
65
77
  * </Box>
66
78
  * ```
67
79
  */
68
- export declare const Box: ({ display, round, gap, padding, margin, observe, throttle, fade, fadeSize, onMeasureChange, onContextChange, className, style, children, ...rest }: BoxProps) => any;
80
+ export declare const Box: ({ display, round, gap, includeRectsFor, padding, margin, observe, observeOffscreen, throttle, fade, fadeSize, onMeasureChange, onContextChange, className, style, children, ...rest }: BoxProps) => any;
@@ -18,9 +18,11 @@ const Box = ({
18
18
  display,
19
19
  round,
20
20
  gap,
21
+ includeRectsFor,
21
22
  padding,
22
23
  margin,
23
24
  observe,
25
+ observeOffscreen,
24
26
  throttle,
25
27
  fade,
26
28
  fadeSize,
@@ -42,9 +44,11 @@ const Box = ({
42
44
  display: display === "block" ? void 0 : display,
43
45
  round: roundAttr(round),
44
46
  gap: gap != null ? "" : void 0,
47
+ "include-rects-for": includeRectsFor,
45
48
  padding: padding != null ? "" : void 0,
46
49
  margin: margin != null ? "" : void 0,
47
50
  observe: observeAttr(observe, onMeasureChange, onContextChange),
51
+ "observe-offscreen": observeOffscreen ? "" : void 0,
48
52
  throttle,
49
53
  fade: fade ? "" : void 0,
50
54
  "fade-size": fade && fadeSize != null ? cssLength(fadeSize) : void 0,
@@ -23,16 +23,24 @@ export interface ExpanderProps extends Omit<BaseProps, "title"> {
23
23
  * bare disclosure).
24
24
  * @defaultValue 'secondary' */
25
25
  priority?: "primary" | "secondary" | "tertiary";
26
- /** Outdent the chevron into the left gutter so the title and body sit
27
- * flush with surrounding content (the docs-header layout). Only takes
28
- * effect with `priority="tertiary"` — on the filled priorities the
29
- * container edge has to bound the chevron, so it's a no-op there. */
26
+ /** Align the title and body with surrounding content on a tertiary
27
+ * expander. With the default start indicator, the mark hangs in the
28
+ * gutter; an end indicator stays inside its edge. */
30
29
  outdent?: boolean;
31
- /** Header actions (e.g. buttons, tags) rendered at the end of the
32
- * header row, OUTSIDE the toggle trigger — clicking them never
33
- * toggles, they're separately focusable, and screen readers see them
34
- * as separate controls. */
30
+ /** Header actions rendered outside the toggle trigger. With an end
31
+ * indicator, actions sit between the title and indicator. */
35
32
  actions?: React.ReactNode;
33
+ /** Built-in disclosure mark, a decorative node, or separate closed/open
34
+ * visuals. Other primitive values leave the indicator empty. Custom nodes
35
+ * are passive; use `actions` for controls.
36
+ * @defaultValue 'chevron' */
37
+ indicator?: "chevron" | "triangle" | "plus" | React.ReactNode | {
38
+ closed: React.ReactNode;
39
+ open: React.ReactNode;
40
+ };
41
+ /** Place the disclosure mark before the title or after the actions.
42
+ * @defaultValue 'start' */
43
+ indicatorPlacement?: "start" | "end";
36
44
  /** Disables the header: not clickable or focusable, hover affordance
37
45
  * off, text dimmed. The open state freezes as-is — disabling an open
38
46
  * expander keeps it open. `actions` stay live; disable them
@@ -76,4 +84,4 @@ export interface ExpanderProps extends Omit<BaseProps, "title"> {
76
84
  * giving real controlled semantics (a consumer can reject a toggle). See
77
85
  * STATEFUL-COMPONENTS.md.
78
86
  */
79
- export declare const Expander: ({ title, level, tone, priority, outdent, actions, disabled, round, open, defaultOpen, onStateChange, className, style, children, ...rest }: ExpanderProps) => any;
87
+ export declare const Expander: ({ title, level, tone, priority, outdent, actions, indicator, indicatorPlacement, disabled, round, open, defaultOpen, onStateChange, className, style, children, ...rest }: ExpanderProps) => any;
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
2
  import { nativeStateChange, neutralToneAttr, toneStyle, roundStyle, roundAttr } from "../anta_helpers";
3
3
  const Expander = ({
4
4
  title,
@@ -7,6 +7,8 @@ const Expander = ({
7
7
  priority,
8
8
  outdent,
9
9
  actions,
10
+ indicator,
11
+ indicatorPlacement,
10
12
  disabled,
11
13
  round,
12
14
  open,
@@ -18,6 +20,10 @@ const Expander = ({
18
20
  ...rest
19
21
  }) => {
20
22
  const controlled = open !== void 0;
23
+ const presetIndicator = typeof indicator === "string" && (indicator === "chevron" || indicator === "triangle" || indicator === "plus") ? indicator : void 0;
24
+ const pairedIndicator = indicator != null && typeof indicator === "object" && "closed" in indicator && "open" in indicator ? indicator : void 0;
25
+ const useCustomSlot = indicator != null && !presetIndicator && !pairedIndicator;
26
+ const customIndicator = typeof indicator === "object" ? indicator : null;
21
27
  const computedStyle = roundStyle(round, "--expander-round", toneStyle(tone, "--expander-tone-source", style));
22
28
  const titleNode = typeof title === "object" && title !== null ? /* @__PURE__ */ jsx("span", { slot: "title", style: { display: "contents" }, children: title }) : /* @__PURE__ */ jsx("a-expander-summary", { slot: "title", children: title });
23
29
  return /* @__PURE__ */ jsxs(
@@ -31,6 +37,8 @@ const Expander = ({
31
37
  outdent: outdent ? "" : void 0,
32
38
  round: roundAttr(round),
33
39
  disabled: disabled ? "" : void 0,
40
+ indicator: presetIndicator,
41
+ "indicator-placement": indicatorPlacement && indicatorPlacement !== "start" ? indicatorPlacement : void 0,
34
42
  onstatechange: onStateChange ? (e) => {
35
43
  const { event, detail, isOwn } = nativeStateChange(e);
36
44
  if (isOwn && detail)
@@ -45,6 +53,11 @@ const Expander = ({
45
53
  children: [
46
54
  titleNode,
47
55
  actions != null && /* @__PURE__ */ jsx("span", { slot: "actions", children: actions }),
56
+ pairedIndicator && /* @__PURE__ */ jsxs(Fragment, { children: [
57
+ /* @__PURE__ */ jsx("span", { slot: "indicator", "data-when": "closed", "aria-hidden": "true", inert: true, children: pairedIndicator.closed }),
58
+ /* @__PURE__ */ jsx("span", { slot: "indicator", "data-when": "open", "aria-hidden": "true", inert: true, children: pairedIndicator.open })
59
+ ] }),
60
+ useCustomSlot && /* @__PURE__ */ jsx("span", { slot: "indicator", "aria-hidden": "true", inert: true, children: customIndicator }),
48
61
  /* @__PURE__ */ jsx("a-expander-details", { children })
49
62
  ]
50
63
  }
@@ -41,10 +41,16 @@ export interface InputProps extends BaseProps, DOMEventHandlers {
41
41
  * success → `circle-check`, info → `info`, brand → `circle-small-solid`); pass a
42
42
  * shape to override, or `false` to drop it. `neutral` has no default glyph. */
43
43
  statusIcon?: IconShape | (string & {}) | false;
44
- /** Custom accent color — any literal CSS color tints the resting + hover
45
- * border (focus ring stays the global `--focus-ring`). For consistency with the
44
+ /** Custom accent color — any literal CSS color tints the border when shown
45
+ * (focus ring stays the global `--focus-ring`). For consistency with the
46
46
  * other controls' custom-tone knob; a `status` still overrides for validation. */
47
47
  tone?: string;
48
+ /** Field emphasis. `primary` has a fill and border, `secondary` keeps the
49
+ * border without a fill, and `tertiary` makes the border transparent until
50
+ * hover or focus.
51
+ * Status borders remain visible at rest.
52
+ * @defaultValue primary */
53
+ priority?: 'primary' | 'secondary' | 'tertiary';
48
54
  /** Size variant. small=24px, medium=28px, large=32px tall; the type scale and
49
55
  * icon track the size (small 13/16 + 14px icon · medium 15/20 + 16px ·
50
56
  * large 17/22 + 18px).
@@ -179,4 +185,4 @@ export interface InputProps extends BaseProps, DOMEventHandlers {
179
185
  * <Input label="Email" type="email" placeholder="you@example.com" clearable />
180
186
  * ```
181
187
  */
182
- export declare const Input: ({ label, hint, status, statusIcon, tone, size, round, value, defaultValue, multiline, rows, maxRows, clearable, leading, trailing, type, autoComplete, inputMode, autoFocus, name, placeholder, truncate, disabled, readOnly, button, required, dimActions, spellCheck, maxLength, minLength, pattern, min, max, step, role, onInput, onChange, onValueChange, onClearClick, onClearInput, children, className, style, ...rest }: InputProps) => any;
188
+ export declare const Input: ({ label, hint, status, statusIcon, tone, priority, size, round, value, defaultValue, multiline, rows, maxRows, clearable, leading, trailing, type, autoComplete, inputMode, autoFocus, name, placeholder, truncate, disabled, readOnly, button, required, dimActions, spellCheck, maxLength, minLength, pattern, min, max, step, role, onInput, onChange, onValueChange, onClearClick, onClearInput, children, className, style, ...rest }: InputProps) => any;
@@ -36,6 +36,7 @@ const Input = ({
36
36
  status,
37
37
  statusIcon,
38
38
  tone,
39
+ priority,
39
40
  size,
40
41
  round,
41
42
  value,
@@ -85,6 +86,7 @@ const Input = ({
85
86
  return /* @__PURE__ */ jsxs(
86
87
  "a-input",
87
88
  {
89
+ priority: priority && priority !== "primary" ? priority : void 0,
88
90
  size: size && size !== "medium" ? size : void 0,
89
91
  round: roundAttr(round),
90
92
  value,
@@ -5,9 +5,12 @@ export interface SliderMarker {
5
5
  value: number;
6
6
  label: React.ReactNode;
7
7
  }
8
+ /** Two endpoints of a range slider. */
9
+ export type SliderRangeValue = [number, number];
10
+ export type SliderValue = number | SliderRangeValue;
8
11
  /** Snapshot passed to `onValueChange` and `onValueCommit`. */
9
- export interface SliderChangeAttrs {
10
- value: number;
12
+ export interface SliderChangeAttrs<T extends SliderValue = number> {
13
+ value: T;
11
14
  min: number;
12
15
  max: number;
13
16
  step: number;
@@ -17,11 +20,11 @@ export interface SliderProps extends BaseProps, DOMEventHandlers {
17
20
  /** Visible field label, shown above the rail. A string supplies the slider's
18
21
  * accessible name; give a rich label an explicit `aria-label`. */
19
22
  label?: React.ReactNode;
20
- /** Controlled value. Update it from `onValueChange`. */
21
- value?: number;
23
+ /** Controlled value. A pair of numbers selects a range. Update it from `onValueChange`. */
24
+ value?: number | SliderRangeValue;
22
25
  /** Initial uncontrolled value.
23
26
  * @defaultValue 0 */
24
- defaultValue?: number;
27
+ defaultValue?: number | SliderRangeValue;
25
28
  /** Lowest permitted value.
26
29
  * @defaultValue 0 */
27
30
  min?: number;
@@ -31,7 +34,7 @@ export interface SliderProps extends BaseProps, DOMEventHandlers {
31
34
  /** Smallest keyboard and drag increment.
32
35
  * @defaultValue 1 */
33
36
  step?: number;
34
- /** Form field name. The current numeric value submits under this name. */
37
+ /** Form field name. A range submits two values under this name. */
35
38
  name?: string;
36
39
  /** Disables pointer and keyboard interaction. */
37
40
  disabled?: boolean;
@@ -76,14 +79,23 @@ export interface SliderProps extends BaseProps, DOMEventHandlers {
76
79
  * from the current value. `jump` first moves to the pressed position.
77
80
  * @defaultValue 'drag-only' */
78
81
  trackClick?: 'drag-only' | 'jump';
82
+ /** Fill the rail outside the selected value or range. */
83
+ inverted?: boolean;
79
84
  /** Fires on every keyboard or pointer value change. */
80
- onValueChange?: (event: any, attrs: SliderChangeAttrs) => void;
85
+ onValueChange?: (event: any, attrs: SliderChangeAttrs<SliderValue>) => void;
81
86
  /** Fires after a drag ends and after each keyboard value change. */
82
- onValueCommit?: (event: any, attrs: SliderChangeAttrs) => void;
87
+ onValueCommit?: (event: any, attrs: SliderChangeAttrs<SliderValue>) => void;
83
88
  }
89
+ type TypedSliderProps<T extends SliderValue> = Omit<SliderProps, 'value' | 'defaultValue' | 'onValueChange' | 'onValueCommit'> & {
90
+ value?: T;
91
+ defaultValue?: T;
92
+ onValueChange?: (event: any, attrs: SliderChangeAttrs<T>) => void;
93
+ onValueCommit?: (event: any, attrs: SliderChangeAttrs<T>) => void;
94
+ };
84
95
  /**
85
- * Slider selects one numeric value from a range. Pressing anywhere on its rail
96
+ * Slider selects one numeric value or a pair of range endpoints. Pressing anywhere on its rail
86
97
  * starts a relative drag by default, so the thumb never jumps to the press point.
87
98
  * Requires `@antadesign/anta/elements` on the client.
88
99
  */
89
- export declare const Slider: ({ label, value, defaultValue, min: minProp, max: maxProp, step: stepProp, name, disabled, tone, thumbTone, size, trackSize, thumbSize, thumbFill, round, valueDisplay, valuePrefix, valueSuffix, markers, trackClick, onInput, onChange, onValueChange, onValueCommit, className, style, children, tabIndex, ...rest }: SliderProps) => any;
100
+ export declare const Slider: <T extends SliderValue = number>({ label, value, defaultValue, min: minProp, max: maxProp, step: stepProp, name, disabled, tone, thumbTone, size, trackSize, thumbSize, thumbFill, round, valueDisplay, valuePrefix, valueSuffix, markers, trackClick, inverted, onInput, onChange, onValueChange, onValueCommit, className, style, children, tabIndex, ...rest }: TypedSliderProps<T>) => any;
101
+ export {};
@@ -9,7 +9,7 @@ const bounds = (min, max) => {
9
9
  const attrsOf = (event) => {
10
10
  const element = event?.target ?? {};
11
11
  return {
12
- value: Number(element.value ?? 0),
12
+ value: element.value,
13
13
  min: Number(element.getAttribute?.("min") ?? 0),
14
14
  max: Number(element.getAttribute?.("max") ?? 100),
15
15
  step: Number(element.getAttribute?.("step") ?? 1),
@@ -37,6 +37,7 @@ const Slider = ({
37
37
  valueSuffix,
38
38
  markers,
39
39
  trackClick = "drag-only",
40
+ inverted,
40
41
  onInput,
41
42
  onChange,
42
43
  onValueChange,
@@ -66,6 +67,8 @@ const Slider = ({
66
67
  const step = number(stepProp, 1) > 0 ? number(stepProp, 1) : 1;
67
68
  const explicitAriaLabel = rest["aria-label"];
68
69
  const ariaLabel = (typeof explicitAriaLabel === "string" ? explicitAriaLabel : void 0) ?? (typeof label === "string" || typeof label === "number" ? String(label) : void 0);
70
+ const range = Array.isArray(value) || Array.isArray(defaultValue);
71
+ const serializeValue = (input) => Array.isArray(input) ? `${input[0]} ${input[1]}` : typeof input === "number" ? input : void 0;
69
72
  const resolvedValueDisplay = valueDisplay === "inline" && label == null ? "end" : valueDisplay;
70
73
  const markerPosition = (marker) => {
71
74
  if (marker.value <= min) return "0%";
@@ -92,8 +95,8 @@ const Slider = ({
92
95
  return /* @__PURE__ */ jsxs(
93
96
  "a-slider",
94
97
  {
95
- value,
96
- defaultvalue: defaultValue,
98
+ value: serializeValue(value),
99
+ defaultvalue: serializeValue(defaultValue),
97
100
  min,
98
101
  max,
99
102
  step,
@@ -107,12 +110,13 @@ const Slider = ({
107
110
  "thumb-fill": thumbFill ? "" : void 0,
108
111
  round: roundAttr(round),
109
112
  "track-click": trackClick === "jump" ? "jump" : void 0,
113
+ inverted: inverted ? "" : void 0,
110
114
  "value-display": resolvedValueDisplay === "end" ? void 0 : resolvedValueDisplay,
111
115
  "value-prefix": valuePrefix,
112
116
  "value-suffix": valueSuffix,
113
- role: "slider",
117
+ role: range ? "group" : "slider",
114
118
  "aria-label": ariaLabel,
115
- tabIndex: disabled ? -1 : tabIndex ?? 0,
119
+ tabIndex: disabled || range ? -1 : tabIndex ?? 0,
116
120
  oninput: handleInput,
117
121
  onchange: handleChange,
118
122
  class: className,
@@ -1 +1 @@
1
- @layer base,anta,components,utilities;@layer anta.reset,anta.components,anta.theme;@layer anta.components{a-box{display:block;min-width:0}a-box[display=inline-block]{display:inline-block}a-box[display=flex]{display:flex}a-box[display=inline-flex]{display:inline-flex}a-box[display=grid]{display:grid}a-box[display=inline-grid]{display:inline-grid}a-box[gap]{gap:var(--box-gap, normal);@supports (border-radius: attr(probe type(<length>),1px)){--box-gap: attr(gap type(<length>))}}a-box[padding]{padding:var(--box-padding, 0px);@supports (padding: attr(padding type(*))){--box-padding: attr(padding type(*), 0px)}}a-box[margin]{margin:var(--box-margin, 0px);@supports (margin: attr(margin type(*))){--box-margin: attr(margin type(*), 0px)}}a-box[round]{border-radius:var(--box-round, 999px);@supports (border-radius: attr(probe type(<length>),1px)){--box-round: attr(round type(<length>))}}a-box[fade]{--box-fade-size: 24px;--box-fade-x: linear-gradient(transparent, transparent);--box-fade-y: linear-gradient(transparent, transparent);--box-fade-to-start: to left;--box-fade-to-end: to right}a-box[fade]:is(:state(hidden-start-x),:state(hidden-end-x),:state(hidden-start-y),:state(hidden-end-y)){mask-image:linear-gradient(#000,#000),var(--box-fade-x),var(--box-fade-y);mask-clip:border-box,padding-box,padding-box;mask-origin:border-box,padding-box,padding-box;mask-composite:exclude,add,add;mask-repeat:no-repeat;mask-size:100% 100%}a-box[fade][fade-size]{--box-fade-size: 24px;@supports (border-radius: attr(probe type(<length>),1px)){--box-fade-size: attr(fade-size type(<length>), 24px)}}a-box[fade]:dir(rtl){--box-fade-to-start: to right;--box-fade-to-end: to left}a-box[fade]:state(hidden-end-x){--box-fade-x: linear-gradient(var(--box-fade-to-end), transparent calc(100% - var(--box-fade-size)), black)}a-box[fade]:state(hidden-start-x){--box-fade-x: linear-gradient(var(--box-fade-to-start), transparent calc(100% - var(--box-fade-size)), black)}a-box[fade]:state(hidden-start-x):state(hidden-end-x){--box-fade-x: linear-gradient( to right, black, transparent var(--box-fade-size), transparent calc(100% - var(--box-fade-size)), black )}a-box[fade]:state(hidden-end-y){--box-fade-y: linear-gradient(to bottom, transparent calc(100% - var(--box-fade-size)), black)}a-box[fade]:state(hidden-start-y){--box-fade-y: linear-gradient(to top, transparent calc(100% - var(--box-fade-size)), black)}a-box[fade]:state(hidden-start-y):state(hidden-end-y){--box-fade-y: linear-gradient( to bottom, black, transparent var(--box-fade-size), transparent calc(100% - var(--box-fade-size)), black )}}
1
+ @layer base,anta,components,utilities;@layer anta.reset,anta.components,anta.theme;@property --box-fade-start-x-strength{syntax: "<number>"; inherits: false; initial-value: 0;}@property --box-fade-end-x-strength{syntax: "<number>"; inherits: false; initial-value: 0;}@property --box-fade-start-y-strength{syntax: "<number>"; inherits: false; initial-value: 0;}@property --box-fade-end-y-strength{syntax: "<number>"; inherits: false; initial-value: 0;}@layer anta.components{a-box{display:block;min-width:0;position:relative}a-box[display=inline-block]{display:inline-block}a-box[display=flex]{display:flex}a-box[display=inline-flex]{display:inline-flex}a-box[display=grid]{display:grid}a-box[display=inline-grid]{display:inline-grid}a-box[gap]{gap:var(--box-gap, normal);@supports (border-radius: attr(probe type(<length>),1px)){--box-gap: attr(gap type(<length>))}}a-box[padding]{padding:var(--box-padding, 0px);@supports (padding: attr(padding type(*))){--box-padding: attr(padding type(*), 0px)}}a-box[margin]{margin:var(--box-margin, 0px);@supports (margin: attr(margin type(*))){--box-margin: attr(margin type(*), 0px)}}a-box[round]{border-radius:var(--box-round, 999px);@supports (border-radius: attr(probe type(<length>),1px)){--box-round: attr(round type(<length>))}}a-box[fade]{--box-fade-size: 24px;--box-fade-left-strength: var(--box-fade-start-x-strength);--box-fade-right-strength: var(--box-fade-end-x-strength);transition:--box-fade-start-x-strength .14s ease-out,--box-fade-end-x-strength .14s ease-out,--box-fade-start-y-strength .14s ease-out,--box-fade-end-y-strength .14s ease-out}a-box[fade]:state(fade-mask-active){mask-image:linear-gradient(#000,#000),linear-gradient(to right,rgb(0 0 0 / var(--box-fade-left-strength)),transparent var(--box-fade-size),transparent calc(100% - var(--box-fade-size)),rgb(0 0 0 / var(--box-fade-right-strength))),linear-gradient(to bottom,rgb(0 0 0 / var(--box-fade-start-y-strength)),transparent var(--box-fade-size),transparent calc(100% - var(--box-fade-size)),rgb(0 0 0 / var(--box-fade-end-y-strength)));mask-clip:border-box,padding-box,padding-box;mask-origin:border-box,padding-box,padding-box;mask-composite:exclude,add,add;mask-repeat:no-repeat;mask-size:100% 100%}a-box[fade][fade-size]{--box-fade-size: 24px;@supports (border-radius: attr(probe type(<length>),1px)){--box-fade-size: attr(fade-size type(<length>), 24px)}}a-box[fade]:dir(rtl){--box-fade-left-strength: var(--box-fade-end-x-strength);--box-fade-right-strength: var(--box-fade-start-x-strength)}a-box[fade]:state(hidden-end-x){--box-fade-end-x-strength: 1}a-box[fade]:state(hidden-start-x){--box-fade-start-x-strength: 1}a-box[fade]:state(hidden-end-y){--box-fade-end-y-strength: 1}a-box[fade]:state(hidden-start-y){--box-fade-start-y-strength: 1}@media(prefers-reduced-motion:reduce){a-box[fade]{transition:none}}}
@@ -11,6 +11,8 @@ import './a-box.css';
11
11
  * measurement fields or presets, including `context` and `all`. `fade` keeps
12
12
  * clipping states current without selecting event fields. A box with neither
13
13
  * runs no observers at all, including the shared visibility observer.
14
+ * `observe-offscreen` opts out of the pause: measurement keeps running while
15
+ * the box is off screen, and the box skips the visibility observer entirely.
14
16
  * The `measurement` / `context` / `isTruncated` getters still read on demand.
15
17
  *
16
18
  * Attributes rather than "is a listener attached" on purpose. A listener tally
@@ -1,8 +1,9 @@
1
1
  import { HTMLElementBase } from "../anta_helpers";
2
- import { throttle } from "es-toolkit";
2
+ import { isEqual, throttle } from "es-toolkit";
3
3
  import { boxObservation } from "../box-observation";
4
4
  import "./a-box.css";
5
5
  const stores = /* @__PURE__ */ new WeakMap();
6
+ const FADE_TRANSITION_MS = 140;
6
7
  const MODE_CLASS = /(?:^|\s)(?:dark|light)(?:\s|$)/;
7
8
  function hasModeClass(element) {
8
9
  return element.classList.contains("dark") || element.classList.contains("light");
@@ -200,21 +201,13 @@ function deviceSnapshot(navigator) {
200
201
  mobile: uaData.userAgentData?.mobile ?? /android|mobi|iphone|ipad|ipod/.test(source)
201
202
  };
202
203
  }
203
- function equal(a, b) {
204
- if (a === b) return true;
205
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
206
- const left = a;
207
- const right = b;
208
- const keys = Object.keys(left);
209
- return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]);
210
- }
211
204
  function same(a, b) {
212
- return a !== void 0 && Object.keys(b).every((key) => equal(a[key], b[key]));
205
+ return a !== void 0 && Object.keys(b).every((key) => isEqual(a[key], b[key]));
213
206
  }
214
207
  function changed(previous, current) {
215
208
  if (!previous) return { ...current };
216
209
  return Object.fromEntries(
217
- Object.entries(current).filter(([key, value]) => !equal(previous[key], value))
210
+ Object.entries(current).filter(([key, value]) => !isEqual(previous[key], value))
218
211
  );
219
212
  }
220
213
  function px(value) {
@@ -264,7 +257,7 @@ function rounded(value) {
264
257
  return Math.round(value * 100) / 100;
265
258
  }
266
259
  class ABoxElement extends HTMLElementBase {
267
- static observedAttributes = ["fade", "observe", "throttle"];
260
+ static observedAttributes = ["fade", "observe", "observe-offscreen", "throttle"];
268
261
  #internals = this.attachInternals?.();
269
262
  #store;
270
263
  #resizeObserver;
@@ -276,9 +269,10 @@ class ABoxElement extends HTMLElementBase {
276
269
  #measurementFields = /* @__PURE__ */ new Set();
277
270
  #watchContent = false;
278
271
  #watchScroll = false;
279
- // Filtering and throttling compare against the last event, not the last read.
272
+ // Filtering compares against the last event, not the last read.
280
273
  #reportedMeasurement;
281
274
  #throttledReport;
275
+ #fadeMaskTimer;
282
276
  #context;
283
277
  #measuring = false;
284
278
  #clips = false;
@@ -298,6 +292,7 @@ class ABoxElement extends HTMLElementBase {
298
292
  this.#sync();
299
293
  }
300
294
  disconnectedCallback() {
295
+ this.#clearFadeMask();
301
296
  this.#stopMeasuring();
302
297
  this.#stopReportingContext();
303
298
  this.#store?.unobserveVisibility(this);
@@ -311,8 +306,9 @@ class ABoxElement extends HTMLElementBase {
311
306
  and the later connect would see the flags already set and skip it. */
312
307
  attributeChangedCallback(name, previous, current) {
313
308
  if (previous === current) return;
309
+ if (name === "fade" && current === null) this.#clearFadeMask();
314
310
  if (!this.#store) return;
315
- if (name === "fade" || name === "observe") this.#sync();
311
+ if (name === "fade" || name === "observe" || name === "observe-offscreen") this.#sync();
316
312
  if (name === "throttle") this.#configureThrottle();
317
313
  if (name === "observe" || name === "throttle") this.#queueMeasurement();
318
314
  }
@@ -327,7 +323,7 @@ class ABoxElement extends HTMLElementBase {
327
323
  }
328
324
  /** Allows a nested `Tooltip truncatedOnly` to use Box's clipping decision. */
329
325
  get isTruncated() {
330
- const measurement = this.#readMeasurement();
326
+ const measurement = this.#readMeasurement(false);
331
327
  return measurement.clippedX || measurement.clippedY;
332
328
  }
333
329
  /** Called by the per-window context cache. It is intentionally not a JSX prop. */
@@ -355,11 +351,12 @@ class ABoxElement extends HTMLElementBase {
355
351
  this.#watchContent = this.hasAttribute("fade") || content;
356
352
  this.#watchScroll = this.hasAttribute("fade") || scroll;
357
353
  const wantsMeasurement = this.hasAttribute("fade") || fields.size > 0;
358
- const measure = this.isConnected && wantsMeasurement && (this.#visible ?? this.hasAttribute("fade"));
354
+ const offscreen = this.hasAttribute("observe-offscreen");
355
+ const measure = this.isConnected && wantsMeasurement && (offscreen || (this.#visible ?? this.hasAttribute("fade")));
359
356
  if (measure) this.#startMeasuring();
360
357
  else this.#stopMeasuring();
361
358
  if (this.#measuring) this.#syncMeasurementSources();
362
- if (this.isConnected && wantsMeasurement) this.#store?.observeVisibility(this);
359
+ if (this.isConnected && wantsMeasurement && !offscreen) this.#store?.observeVisibility(this);
363
360
  else this.#store?.unobserveVisibility(this);
364
361
  if (this.isConnected && wantsContext) this.#startReportingContext();
365
362
  else this.#stopReportingContext();
@@ -391,7 +388,7 @@ class ABoxElement extends HTMLElementBase {
391
388
  this.#resizeObserver = new this.view.ResizeObserver(this.#queueMeasurement);
392
389
  this.#resizeObserver.observe(this);
393
390
  this.doc.fonts?.addEventListener("loadingdone", this.#queueMeasurement);
394
- this.#measurement = this.#readMeasurement();
391
+ this.#measurement = this.#readMeasurement(false);
395
392
  this.#setMeasurementStates(this.#measurement);
396
393
  this.#syncChildObservation();
397
394
  this.#initialMeasurement = true;
@@ -488,7 +485,8 @@ class ABoxElement extends HTMLElementBase {
488
485
  this.#frame = this.view.requestAnimationFrame(() => {
489
486
  this.#frame = void 0;
490
487
  if (!this.#measuring) return;
491
- this.#reportMeasurement();
488
+ if (this.#throttledReport) this.#throttledReport();
489
+ else this.#reportMeasurement();
492
490
  });
493
491
  };
494
492
  #queueContext = () => {
@@ -502,7 +500,30 @@ class ABoxElement extends HTMLElementBase {
502
500
  this.#reportContext(initial);
503
501
  });
504
502
  };
505
- #readMeasurement() {
503
+ #readRects(rect) {
504
+ let rects = [];
505
+ const selector = this.getAttribute("include-rects-for");
506
+ if (selector) {
507
+ try {
508
+ const boxRect = rect ?? this.getBoundingClientRect();
509
+ rects = Array.from(this.querySelectorAll(selector), (element) => {
510
+ const target = element.getBoundingClientRect();
511
+ return {
512
+ top: rounded(target.top - boxRect.top),
513
+ right: rounded(target.right - boxRect.left),
514
+ bottom: rounded(target.bottom - boxRect.top),
515
+ left: rounded(target.left - boxRect.left),
516
+ width: rounded(target.width),
517
+ height: rounded(target.height)
518
+ };
519
+ });
520
+ } catch (error) {
521
+ if (!(error instanceof this.view.DOMException) || error.name !== "SyntaxError") throw error;
522
+ }
523
+ }
524
+ return rects;
525
+ }
526
+ #readMeasurement(includeRects = true) {
506
527
  const rect = this.getBoundingClientRect();
507
528
  const clientWidth = this.clientWidth;
508
529
  const clientHeight = this.clientHeight;
@@ -524,6 +545,7 @@ class ABoxElement extends HTMLElementBase {
524
545
  return {
525
546
  width: rounded(rect.width),
526
547
  height: rounded(rect.height),
548
+ rects: includeRects ? this.#readRects(rect) : [],
527
549
  clientWidth,
528
550
  clientHeight,
529
551
  scrollWidth,
@@ -559,7 +581,7 @@ class ABoxElement extends HTMLElementBase {
559
581
  this.#throttledReport = void 0;
560
582
  const interval = Number(this.getAttribute("throttle"));
561
583
  if (this.#measuring && Number.isFinite(interval) && interval > 0) {
562
- this.#throttledReport = throttle(this.#emitMeasurement, Math.min(interval, 2147483647));
584
+ this.#throttledReport = throttle(() => this.#reportMeasurement(), Math.min(interval, 2147483647));
563
585
  }
564
586
  }
565
587
  #hasMeasurementChange(current) {
@@ -572,27 +594,22 @@ class ABoxElement extends HTMLElementBase {
572
594
  return false;
573
595
  }
574
596
  #reportMeasurement() {
575
- const current = this.#readMeasurement();
597
+ const current = this.#readMeasurement(false);
576
598
  this.#measurement = current;
577
599
  this.#setMeasurementStates(current);
578
600
  this.#syncChildObservation();
579
601
  if (!this.#hasMeasurementChange(current)) return;
580
- if (this.#throttledReport) this.#throttledReport();
581
- else this.#emitMeasurement();
582
- }
583
- #emitMeasurement = () => {
584
- const current = this.#measurement;
585
- if (!this.#measuring || !current || !this.#hasMeasurementChange(current)) return;
602
+ const snapshot = { ...current, rects: this.#readRects() };
586
603
  const initial = this.#initialMeasurement;
587
604
  const previous = this.#reportedMeasurement;
588
605
  this.#initialMeasurement = false;
589
- this.#reportedMeasurement = current;
606
+ this.#reportedMeasurement = snapshot;
590
607
  const detail = {
591
- changed: initial ? { ...current } : changed(previous, current),
592
- current
608
+ changed: changed(initial ? void 0 : previous, snapshot),
609
+ current: snapshot
593
610
  };
594
611
  this.dispatchEvent(new this.view.CustomEvent("measurechange", { detail }));
595
- };
612
+ }
596
613
  #reportContext(initial) {
597
614
  const current = this.#readContext();
598
615
  const previous = this.#context;
@@ -620,6 +637,31 @@ class ABoxElement extends HTMLElementBase {
620
637
  this.#setState("hidden-end-x", measurement.hiddenEndX);
621
638
  this.#setState("hidden-start-y", measurement.hiddenStartY);
622
639
  this.#setState("hidden-end-y", measurement.hiddenEndY);
640
+ this.#syncFadeMask(measurement);
641
+ }
642
+ #syncFadeMask(measurement) {
643
+ const hidden = measurement.hiddenStartX || measurement.hiddenEndX || measurement.hiddenStartY || measurement.hiddenEndY;
644
+ if (hidden && this.hasAttribute("fade")) {
645
+ if (this.#fadeMaskTimer !== void 0) this.view.clearTimeout(this.#fadeMaskTimer);
646
+ this.#fadeMaskTimer = void 0;
647
+ this.#setState("fade-mask-active", true);
648
+ return;
649
+ }
650
+ if (!this.hasAttribute("fade")) {
651
+ this.#clearFadeMask();
652
+ return;
653
+ }
654
+ if (!this.#internals?.states?.has("fade-mask-active") || this.#fadeMaskTimer !== void 0) return;
655
+ if (this.view.matchMedia("(prefers-reduced-motion: reduce)").matches) {
656
+ this.#clearFadeMask();
657
+ return;
658
+ }
659
+ this.#fadeMaskTimer = this.view.setTimeout(() => this.#clearFadeMask(), FADE_TRANSITION_MS);
660
+ }
661
+ #clearFadeMask() {
662
+ if (this.#fadeMaskTimer !== void 0) this.view.clearTimeout(this.#fadeMaskTimer);
663
+ this.#fadeMaskTimer = void 0;
664
+ this.#setState("fade-mask-active", false);
623
665
  }
624
666
  #setState(name, active) {
625
667
  if (active) this.#internals?.states?.add(name);
@@ -82,10 +82,8 @@ class AButtonElement extends HTMLElementBase {
82
82
  }
83
83
  function findForm(el) {
84
84
  const formId = el.getAttribute("form");
85
- if (formId) {
86
- return el.ownerDocument.getElementById(formId);
87
- }
88
- return el.closest("form");
85
+ const candidate = formId === null ? el.closest("form") : el.ownerDocument.getElementById(formId);
86
+ return candidate?.namespaceURI === "http://www.w3.org/1999/xhtml" && candidate.localName === "form" ? candidate : null;
89
87
  }
90
88
  function handleClick(e) {
91
89
  const el = e.target.closest(
@@ -114,6 +114,8 @@ const SHADOW_STYLE = `
114
114
  display: none;
115
115
  flex: 0 0 auto;
116
116
  padding: var(--dialog-header-padding, 18px 20px 8px);
117
+ overflow: clip;
118
+ overflow-wrap: anywhere;
117
119
  font-size: 17px;
118
120
  font-weight: 560;
119
121
  line-height: 24px;