@mindees/atlas 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/a11y.d.ts CHANGED
@@ -14,6 +14,7 @@ interface A11yState {
14
14
  disabled?: boolean;
15
15
  selected?: boolean;
16
16
  checked?: boolean;
17
+ pressed?: boolean;
17
18
  expanded?: boolean;
18
19
  busy?: boolean;
19
20
  hidden?: boolean;
@@ -30,14 +31,25 @@ interface A11yProps {
30
31
  describedBy?: string;
31
32
  /** Live-region politeness (`aria-live`). */
32
33
  live?: 'off' | 'polite' | 'assertive';
33
- /** Accessibility state (`aria-disabled`/`-selected`/`-checked`/`-expanded`/`-busy`/`aria-hidden`). */
34
- state?: A11yState;
34
+ /**
35
+ * Accessibility state → `aria-*`. Pass an **accessor** (`() => ({ checked: on() })`) to make the
36
+ * `aria-*` attributes reactive — a static object bakes them once, so a screen reader never hears
37
+ * a toggle change. Reactive keys are those present on the first read (stable shape).
38
+ */
39
+ state?: A11yState | (() => A11yState);
40
+ /** Current value of a range widget (`aria-valuenow`); accessor → reactive. */
41
+ valueNow?: number | (() => number);
42
+ /** Minimum of a range widget (`aria-valuemin`). */
43
+ valueMin?: number;
44
+ /** Maximum of a range widget (`aria-valuemax`). */
45
+ valueMax?: number;
35
46
  }
36
47
  /**
37
- * Lower {@link A11yProps} to a host prop bag of `role` + `aria-*` (only keys that are defined,
38
- * so omitted props stay omitted exactOptionalPropertyTypes-safe).
48
+ * Lower {@link A11yProps} to a host prop bag of `role` + `aria-*` (only defined keys, so omitted
49
+ * props stay omitted). Accessor-valued `state`/`valueNow` lower to **reactive** attribute bindings
50
+ * (the renderer re-applies them via `setProp`), so accessibility tracks state changes.
39
51
  */
40
- declare function toA11yProps(a11y: A11yProps): Record<string, string>;
52
+ declare function toA11yProps(a11y: A11yProps): Record<string, unknown>;
41
53
  //#endregion
42
54
  export { A11yProps, A11yState, Role, toA11yProps };
43
55
  //# sourceMappingURL=a11y.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"a11y.d.ts","names":[],"sources":["../src/a11y.ts"],"mappings":";;AAUA;;;;AAAgB;AAuBhB;;;KAvBY,IAAA;;UAuBK,SAAA;EACf,QAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA;EACA,IAAA;EACA,MAAA;AAAA;;UAIe,SAAA;EAYE;EAVjB,IAAA,GAAO,IAAA;EAAA;EAEP,KAAA;EAEA;EAAA,UAAA;EAIA;EAFA,WAAA;EAIQ;EAFR,IAAA;EAEiB;EAAjB,KAAA,GAAQ,SAAS;AAAA;;;;;iBAOH,WAAA,CAAY,IAAA,EAAM,SAAA,GAAY,MAAM"}
1
+ {"version":3,"file":"a11y.d.ts","names":[],"sources":["../src/a11y.ts"],"mappings":";;AAUA;;;;AAAgB;AAuBhB;;;KAvBY,IAAA;;UAuBK,SAAA;EACf,QAAA;EACA,QAAA;EACA,OAAA;EACA,OAAA;EACA,QAAA;EACA,IAAA;EACA,MAAA;AAAA;;UAee,SAAA;EAER;EAAP,IAAA,GAAO,IAAA;EAcoB;EAZ3B,KAAA;EAYoC;EAVpC,UAAA;EAJO;EAMP,WAAA;EAFA;EAIA,IAAA;EAAA;;;;;EAMA,KAAA,GAAQ,SAAA,UAAmB,SAAA;EAM3B;EAJA,QAAA;EAIQ;EAFR,QAAA;EAUyB;EARzB,QAAA;AAAA;;;;;AAQkD;iBAApC,WAAA,CAAY,IAAA,EAAM,SAAA,GAAY,MAAM"}
package/dist/a11y.js CHANGED
@@ -1,7 +1,18 @@
1
1
  //#region src/a11y.ts
2
+ /** The boolean state keys, paired with their `aria-*` attribute. */
3
+ const STATE_ARIA = [
4
+ ["disabled", "aria-disabled"],
5
+ ["selected", "aria-selected"],
6
+ ["checked", "aria-checked"],
7
+ ["pressed", "aria-pressed"],
8
+ ["expanded", "aria-expanded"],
9
+ ["busy", "aria-busy"],
10
+ ["hidden", "aria-hidden"]
11
+ ];
2
12
  /**
3
- * Lower {@link A11yProps} to a host prop bag of `role` + `aria-*` (only keys that are defined,
4
- * so omitted props stay omitted exactOptionalPropertyTypes-safe).
13
+ * Lower {@link A11yProps} to a host prop bag of `role` + `aria-*` (only defined keys, so omitted
14
+ * props stay omitted). Accessor-valued `state`/`valueNow` lower to **reactive** attribute bindings
15
+ * (the renderer re-applies them via `setProp`), so accessibility tracks state changes.
5
16
  */
6
17
  function toA11yProps(a11y) {
7
18
  const out = {};
@@ -11,14 +22,23 @@ function toA11yProps(a11y) {
11
22
  if (a11y.describedBy !== void 0) out["aria-describedby"] = a11y.describedBy;
12
23
  if (a11y.live !== void 0) out["aria-live"] = a11y.live;
13
24
  const s = a11y.state;
14
- if (s) {
15
- if (s.disabled !== void 0) out["aria-disabled"] = String(s.disabled);
16
- if (s.selected !== void 0) out["aria-selected"] = String(s.selected);
17
- if (s.checked !== void 0) out["aria-checked"] = String(s.checked);
18
- if (s.expanded !== void 0) out["aria-expanded"] = String(s.expanded);
19
- if (s.busy !== void 0) out["aria-busy"] = String(s.busy);
20
- if (s.hidden !== void 0) out["aria-hidden"] = String(s.hidden);
25
+ if (typeof s === "function") {
26
+ const initial = s();
27
+ for (const [key, attr] of STATE_ARIA) {
28
+ if (initial[key] === void 0) continue;
29
+ out[attr] = () => {
30
+ const v = s()[key];
31
+ return v === void 0 ? void 0 : String(v);
32
+ };
33
+ }
34
+ } else if (s) {
35
+ for (const [key, attr] of STATE_ARIA) if (s[key] !== void 0) out[attr] = String(s[key]);
21
36
  }
37
+ if (a11y.valueMin !== void 0) out["aria-valuemin"] = String(a11y.valueMin);
38
+ if (a11y.valueMax !== void 0) out["aria-valuemax"] = String(a11y.valueMax);
39
+ const vn = a11y.valueNow;
40
+ if (typeof vn === "function") out["aria-valuenow"] = () => String(vn());
41
+ else if (vn !== void 0) out["aria-valuenow"] = String(vn);
22
42
  return out;
23
43
  }
24
44
  //#endregion
package/dist/a11y.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"a11y.js","names":[],"sources":["../src/a11y.ts"],"sourcesContent":["/**\n * Atlas accessibility — a single typed `A11yProps` surface lowered to `role` + `aria-*`\n * attribute props. The DOM backend passes these through verbatim (`setAttribute`), so web a11y\n * is real; native hosts receive them as serialized props (interpretation is a 🔬 research-track\n * host concern — carried, never silently dropped). See `docs/adr/0022-atlas-primitives.md`.\n *\n * @module\n */\n\n/** A WAI-ARIA-ish role, mapped straight to the host `role` attribute on web. */\nexport type Role =\n | 'button'\n | 'link'\n | 'image'\n | 'heading'\n | 'list'\n | 'listitem'\n | 'text'\n | 'textbox'\n | 'checkbox'\n | 'switch'\n | 'radio'\n | 'tab'\n | 'tabpanel'\n | 'dialog'\n | 'alert'\n | 'status'\n | 'separator'\n | 'progressbar'\n | 'none'\n | 'presentation'\n\n/** Accessibility state, lowered to the matching `aria-*` attributes. */\nexport interface A11yState {\n disabled?: boolean\n selected?: boolean\n checked?: boolean\n expanded?: boolean\n busy?: boolean\n hidden?: boolean\n}\n\n/** The accessibility surface every Atlas primitive accepts. */\nexport interface A11yProps {\n /** ARIA role (web `role`). */\n role?: Role\n /** Accessible name (`aria-label`). */\n label?: string\n /** Id(s) of the element(s) labelling this one (`aria-labelledby`). */\n labelledBy?: string\n /** Id(s) of the element(s) describing this one (`aria-describedby`). */\n describedBy?: string\n /** Live-region politeness (`aria-live`). */\n live?: 'off' | 'polite' | 'assertive'\n /** Accessibility state (`aria-disabled`/`-selected`/`-checked`/`-expanded`/`-busy`/`aria-hidden`). */\n state?: A11yState\n}\n\n/**\n * Lower {@link A11yProps} to a host prop bag of `role` + `aria-*` (only keys that are defined,\n * so omitted props stay omitted exactOptionalPropertyTypes-safe).\n */\nexport function toA11yProps(a11y: A11yProps): Record<string, string> {\n const out: Record<string, string> = {}\n if (a11y.role !== undefined) out.role = a11y.role\n if (a11y.label !== undefined) out['aria-label'] = a11y.label\n if (a11y.labelledBy !== undefined) out['aria-labelledby'] = a11y.labelledBy\n if (a11y.describedBy !== undefined) out['aria-describedby'] = a11y.describedBy\n if (a11y.live !== undefined) out['aria-live'] = a11y.live\n const s = a11y.state\n if (s) {\n if (s.disabled !== undefined) out['aria-disabled'] = String(s.disabled)\n if (s.selected !== undefined) out['aria-selected'] = String(s.selected)\n if (s.checked !== undefined) out['aria-checked'] = String(s.checked)\n if (s.expanded !== undefined) out['aria-expanded'] = String(s.expanded)\n if (s.busy !== undefined) out['aria-busy'] = String(s.busy)\n if (s.hidden !== undefined) out['aria-hidden'] = String(s.hidden)\n }\n return out\n}\n"],"mappings":";;;;;AA8DA,SAAgB,YAAY,MAAyC;CACnE,MAAM,MAA8B,CAAC;CACrC,IAAI,KAAK,SAAS,KAAA,GAAW,IAAI,OAAO,KAAK;CAC7C,IAAI,KAAK,UAAU,KAAA,GAAW,IAAI,gBAAgB,KAAK;CACvD,IAAI,KAAK,eAAe,KAAA,GAAW,IAAI,qBAAqB,KAAK;CACjE,IAAI,KAAK,gBAAgB,KAAA,GAAW,IAAI,sBAAsB,KAAK;CACnE,IAAI,KAAK,SAAS,KAAA,GAAW,IAAI,eAAe,KAAK;CACrD,MAAM,IAAI,KAAK;CACf,IAAI,GAAG;EACL,IAAI,EAAE,aAAa,KAAA,GAAW,IAAI,mBAAmB,OAAO,EAAE,QAAQ;EACtE,IAAI,EAAE,aAAa,KAAA,GAAW,IAAI,mBAAmB,OAAO,EAAE,QAAQ;EACtE,IAAI,EAAE,YAAY,KAAA,GAAW,IAAI,kBAAkB,OAAO,EAAE,OAAO;EACnE,IAAI,EAAE,aAAa,KAAA,GAAW,IAAI,mBAAmB,OAAO,EAAE,QAAQ;EACtE,IAAI,EAAE,SAAS,KAAA,GAAW,IAAI,eAAe,OAAO,EAAE,IAAI;EAC1D,IAAI,EAAE,WAAW,KAAA,GAAW,IAAI,iBAAiB,OAAO,EAAE,MAAM;CAClE;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"a11y.js","names":[],"sources":["../src/a11y.ts"],"sourcesContent":["/**\n * Atlas accessibility — a single typed `A11yProps` surface lowered to `role` + `aria-*`\n * attribute props. The DOM backend passes these through verbatim (`setAttribute`), so web a11y\n * is real; native hosts receive them as serialized props (interpretation is a 🔬 research-track\n * host concern — carried, never silently dropped). See `docs/adr/0022-atlas-primitives.md`.\n *\n * @module\n */\n\n/** A WAI-ARIA-ish role, mapped straight to the host `role` attribute on web. */\nexport type Role =\n | 'button'\n | 'link'\n | 'image'\n | 'heading'\n | 'list'\n | 'listitem'\n | 'text'\n | 'textbox'\n | 'checkbox'\n | 'switch'\n | 'radio'\n | 'tab'\n | 'tabpanel'\n | 'dialog'\n | 'alert'\n | 'status'\n | 'separator'\n | 'progressbar'\n | 'none'\n | 'presentation'\n\n/** Accessibility state, lowered to the matching `aria-*` attributes. */\nexport interface A11yState {\n disabled?: boolean\n selected?: boolean\n checked?: boolean\n pressed?: boolean\n expanded?: boolean\n busy?: boolean\n hidden?: boolean\n}\n\n/** The boolean state keys, paired with their `aria-*` attribute. */\nconst STATE_ARIA = [\n ['disabled', 'aria-disabled'],\n ['selected', 'aria-selected'],\n ['checked', 'aria-checked'],\n ['pressed', 'aria-pressed'],\n ['expanded', 'aria-expanded'],\n ['busy', 'aria-busy'],\n ['hidden', 'aria-hidden'],\n] as const\n\n/** The accessibility surface every Atlas primitive accepts. */\nexport interface A11yProps {\n /** ARIA role (web `role`). */\n role?: Role\n /** Accessible name (`aria-label`). */\n label?: string\n /** Id(s) of the element(s) labelling this one (`aria-labelledby`). */\n labelledBy?: string\n /** Id(s) of the element(s) describing this one (`aria-describedby`). */\n describedBy?: string\n /** Live-region politeness (`aria-live`). */\n live?: 'off' | 'polite' | 'assertive'\n /**\n * Accessibility state → `aria-*`. Pass an **accessor** (`() => ({ checked: on() })`) to make the\n * `aria-*` attributes reactive — a static object bakes them once, so a screen reader never hears\n * a toggle change. Reactive keys are those present on the first read (stable shape).\n */\n state?: A11yState | (() => A11yState)\n /** Current value of a range widget (`aria-valuenow`); accessor → reactive. */\n valueNow?: number | (() => number)\n /** Minimum of a range widget (`aria-valuemin`). */\n valueMin?: number\n /** Maximum of a range widget (`aria-valuemax`). */\n valueMax?: number\n}\n\n/**\n * Lower {@link A11yProps} to a host prop bag of `role` + `aria-*` (only defined keys, so omitted\n * props stay omitted). Accessor-valued `state`/`valueNow` lower to **reactive** attribute bindings\n * (the renderer re-applies them via `setProp`), so accessibility tracks state changes.\n */\nexport function toA11yProps(a11y: A11yProps): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n if (a11y.role !== undefined) out.role = a11y.role\n if (a11y.label !== undefined) out['aria-label'] = a11y.label\n if (a11y.labelledBy !== undefined) out['aria-labelledby'] = a11y.labelledBy\n if (a11y.describedBy !== undefined) out['aria-describedby'] = a11y.describedBy\n if (a11y.live !== undefined) out['aria-live'] = a11y.live\n\n const s = a11y.state\n if (typeof s === 'function') {\n const initial = s()\n for (const [key, attr] of STATE_ARIA) {\n if (initial[key] === undefined) continue\n out[attr] = () => {\n const v = s()[key]\n return v === undefined ? undefined : String(v)\n }\n }\n } else if (s) {\n for (const [key, attr] of STATE_ARIA) {\n if (s[key] !== undefined) out[attr] = String(s[key])\n }\n }\n\n if (a11y.valueMin !== undefined) out['aria-valuemin'] = String(a11y.valueMin)\n if (a11y.valueMax !== undefined) out['aria-valuemax'] = String(a11y.valueMax)\n const vn = a11y.valueNow\n if (typeof vn === 'function') out['aria-valuenow'] = () => String(vn())\n else if (vn !== undefined) out['aria-valuenow'] = String(vn)\n\n return out\n}\n"],"mappings":";;AA4CA,MAAM,aAAa;CACjB,CAAC,YAAY,eAAe;CAC5B,CAAC,YAAY,eAAe;CAC5B,CAAC,WAAW,cAAc;CAC1B,CAAC,WAAW,cAAc;CAC1B,CAAC,YAAY,eAAe;CAC5B,CAAC,QAAQ,WAAW;CACpB,CAAC,UAAU,aAAa;AAC1B;;;;;;AAiCA,SAAgB,YAAY,MAA0C;CACpE,MAAM,MAA+B,CAAC;CACtC,IAAI,KAAK,SAAS,KAAA,GAAW,IAAI,OAAO,KAAK;CAC7C,IAAI,KAAK,UAAU,KAAA,GAAW,IAAI,gBAAgB,KAAK;CACvD,IAAI,KAAK,eAAe,KAAA,GAAW,IAAI,qBAAqB,KAAK;CACjE,IAAI,KAAK,gBAAgB,KAAA,GAAW,IAAI,sBAAsB,KAAK;CACnE,IAAI,KAAK,SAAS,KAAA,GAAW,IAAI,eAAe,KAAK;CAErD,MAAM,IAAI,KAAK;CACf,IAAI,OAAO,MAAM,YAAY;EAC3B,MAAM,UAAU,EAAE;EAClB,KAAK,MAAM,CAAC,KAAK,SAAS,YAAY;GACpC,IAAI,QAAQ,SAAS,KAAA,GAAW;GAChC,IAAI,cAAc;IAChB,MAAM,IAAI,EAAE,EAAE;IACd,OAAO,MAAM,KAAA,IAAY,KAAA,IAAY,OAAO,CAAC;GAC/C;EACF;CACF,OAAO,IAAI;OACJ,MAAM,CAAC,KAAK,SAAS,YACxB,IAAI,EAAE,SAAS,KAAA,GAAW,IAAI,QAAQ,OAAO,EAAE,IAAI;CAAA;CAIvD,IAAI,KAAK,aAAa,KAAA,GAAW,IAAI,mBAAmB,OAAO,KAAK,QAAQ;CAC5E,IAAI,KAAK,aAAa,KAAA,GAAW,IAAI,mBAAmB,OAAO,KAAK,QAAQ;CAC5E,MAAM,KAAK,KAAK;CAChB,IAAI,OAAO,OAAO,YAAY,IAAI,yBAAyB,OAAO,GAAG,CAAC;MACjE,IAAI,OAAO,KAAA,GAAW,IAAI,mBAAmB,OAAO,EAAE;CAE3D,OAAO;AACT"}
@@ -78,6 +78,20 @@ interface ProgressBarProps extends BaseProps {
78
78
  readonly height?: number;
79
79
  }
80
80
  declare const ProgressBar: Component<ProgressBarProps>;
81
+ /**
82
+ * A spinning loading indicator. Emits the `activityindicator` host tag, which each backend
83
+ * renders natively: web → a CSS keyframe spinner (size from `width`/`height`, arc from `color`),
84
+ * Android → an indeterminate `ProgressBar`. Size/color flow through ordinary style keys.
85
+ */
86
+ interface ActivityIndicatorProps extends BaseProps {
87
+ /** Diameter in px (default 24). */
88
+ readonly size?: number;
89
+ /** Spinner color (defaults to the theme primary). */
90
+ readonly color?: string;
91
+ /** When false, renders nothing (so callers can gate it without a conditional). */
92
+ readonly animating?: boolean;
93
+ }
94
+ declare const ActivityIndicator: Component<ActivityIndicatorProps>;
81
95
  //#endregion
82
- export { Avatar, AvatarProps, Badge, BadgeProps, Card, CardProps, Chip, ChipProps, Divider, DividerProps, KeyboardAvoidingView, KeyboardAvoidingViewProps, ProgressBar, ProgressBarProps, SafeAreaView, SafeAreaViewProps, Switch, SwitchProps };
96
+ export { ActivityIndicator, ActivityIndicatorProps, Avatar, AvatarProps, Badge, BadgeProps, Card, CardProps, Chip, ChipProps, Divider, DividerProps, KeyboardAvoidingView, KeyboardAvoidingViewProps, ProgressBar, ProgressBarProps, SafeAreaView, SafeAreaViewProps, Switch, SwitchProps };
83
97
  //# sourceMappingURL=components.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"components.d.ts","names":[],"sources":["../src/components.ts"],"mappings":";;;;;;UA8CiB,SAAA,SAAkB,SAAS;EAAA,SACjC,QAAA,GAAW,WAAA;EAQT;EAAA,SANF,OAAA;;WAEA,OAAA;EAI2B;EAAA,SAF3B,MAAA;AAAA;AAAA,cAEE,IAAA,EAAM,SAAS,CAAC,SAAA;;UA4BZ,YAAA,SAAqB,SAAS;EAAA,SACpC,WAAA;EAAA,SACA,SAAA;EAEA;EAAA,SAAA,KAAA;AAAA;AAAA,cAEE,OAAA,EAAS,SAAS,CAAC,YAAA;AAAA,KAoBpB,SAAA;;UAUK,UAAA,SAAmB,SAAA;EAAA,SACzB,QAAA,GAAW,WAAA;EAAA,SACX,IAAA,GAAO,SAAA;AAAA;AAAA,cAEL,KAAA,EAAO,SAAS,CAAC,UAAA;;UAwCb,WAAA,SAAoB,SAAS;EAAA,SACnC,GAAA;EAAA,SACA,IAAA;;WAEA,IAAA;AAAA;AAAA,cAEE,MAAA,EAAQ,SAAS,CAAC,WAAA;;UA2Cd,SAAA,SAAkB,IAAA,CAAK,SAAA;EAAA,SAC7B,KAAA;EAAA,SACA,QAAA,GAAW,QAAA;EAAA,SACX,QAAA;EAAA,SACA,OAAA;EAAA,SACA,OAAA,GAAU,WAAA;EAAA,SACV,QAAA,GAAW,WAAA;EAAA,SACX,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;AAAA,cAEf,IAAA,EAAM,SAAS,CAAC,SAAA;;UAuDZ,WAAA,SAAoB,IAAA,CAAK,SAAA;EAzJF;EAAA,SA2J7B,KAAA,EAAO,QAAA;EAAA,SACP,aAAA,IAAiB,KAAA;EAAA,SACjB,QAAA;EAAA,SACA,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;AAAA,cAEf,MAAA,EAAQ,SAAS,CAAC,WAAA;;UA4Cd,iBAAA,SAA0B,SAAA;EAAA,SAChC,QAAA,GAAW,WAAA;EAjKP;EAAA,SAmKJ,KAAA,GAAQ,aAAA;AAAA;AAAA,cAEN,YAAA,EAAc,SAAS,CAAC,iBAAA;;UAsBpB,yBAAA,SAAkC,SAAS;EAAA,SACjD,QAAA,GAAW,WAAA;AAAA;AAAA,cAET,oBAAA,EAAsB,SAAS,CAAC,yBAAA;;UAY5B,gBAAA,SAAyB,SAAS;EAxJ9B;EAAA,SA0JV,KAAA,GAAQ,QAAA;EAAA,SACR,UAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;AAAA;AAAA,cAEE,WAAA,EAAa,SAAS,CAAC,gBAAA"}
1
+ {"version":3,"file":"components.d.ts","names":[],"sources":["../src/components.ts"],"mappings":";;;;;;UA8CiB,SAAA,SAAkB,SAAS;EAAA,SACjC,QAAA,GAAW,WAAA;EAQT;EAAA,SANF,OAAA;;WAEA,OAAA;EAI2B;EAAA,SAF3B,MAAA;AAAA;AAAA,cAEE,IAAA,EAAM,SAAS,CAAC,SAAA;;UA4BZ,YAAA,SAAqB,SAAS;EAAA,SACpC,WAAA;EAAA,SACA,SAAA;EAEA;EAAA,SAAA,KAAA;AAAA;AAAA,cAEE,OAAA,EAAS,SAAS,CAAC,YAAA;AAAA,KAoBpB,SAAA;;UAUK,UAAA,SAAmB,SAAA;EAAA,SACzB,QAAA,GAAW,WAAA;EAAA,SACX,IAAA,GAAO,SAAA;AAAA;AAAA,cAEL,KAAA,EAAO,SAAS,CAAC,UAAA;;UAwCb,WAAA,SAAoB,SAAS;EAAA,SACnC,GAAA;EAAA,SACA,IAAA;;WAEA,IAAA;AAAA;AAAA,cAEE,MAAA,EAAQ,SAAS,CAAC,WAAA;;UA2Cd,SAAA,SAAkB,IAAA,CAAK,SAAA;EAAA,SAC7B,KAAA;EAAA,SACA,QAAA,GAAW,QAAA;EAAA,SACX,QAAA;EAAA,SACA,OAAA;EAAA,SACA,OAAA,GAAU,WAAA;EAAA,SACV,QAAA,GAAW,WAAA;EAAA,SACX,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;AAAA,cAEf,IAAA,EAAM,SAAS,CAAC,SAAA;;UA4DZ,WAAA,SAAoB,IAAA,CAAK,SAAA;EA9JF;EAAA,SAgK7B,KAAA,EAAO,QAAA;EAAA,SACP,aAAA,IAAiB,KAAA;EAAA,SACjB,QAAA;EAAA,SACA,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;AAAA,cAEf,MAAA,EAAQ,SAAS,CAAC,WAAA;;UAgDd,iBAAA,SAA0B,SAAA;EAAA,SAChC,QAAA,GAAW,WAAA;EA1KP;EAAA,SA4KJ,KAAA,GAAQ,aAAA;AAAA;AAAA,cAEN,YAAA,EAAc,SAAS,CAAC,iBAAA;;UAsBpB,yBAAA,SAAkC,SAAS;EAAA,SACjD,QAAA,GAAW,WAAA;AAAA;AAAA,cAET,oBAAA,EAAsB,SAAS,CAAC,yBAAA;;UAY5B,gBAAA,SAAyB,SAAS;EAjK9B;EAAA,SAmKV,KAAA,GAAQ,QAAA;EAAA,SACR,UAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;AAAA;AAAA,cAEE,WAAA,EAAa,SAAS,CAAC,gBAAA;;;;;;UAyCnB,sBAAA,SAA+B,SAAS;EAlN9C;EAAA,SAoNA,IAAA;EAnNU;EAAA,SAqNV,KAAA;EApNW;EAAA,SAsNX,SAAA;AAAA;AAAA,cAEE,iBAAA,EAAmB,SAAS,CAAC,sBAAA"}
@@ -1,5 +1,6 @@
1
1
  import { useKeyboard, useSafeAreaInsets } from "./environment.js";
2
2
  import { flattenStyle } from "./style.js";
3
+ import { toHostProps } from "./host.js";
3
4
  import { Image, Pressable, Text, View } from "./primitives.js";
4
5
  import { fontWeight, radius, space, useTheme } from "./tokens.js";
5
6
  import { createElement } from "@mindees/core";
@@ -187,6 +188,10 @@ const Chip = (props) => {
187
188
  return createElement(Pressable, {
188
189
  ...rest,
189
190
  role: rest.role ?? "button",
191
+ state: () => ({
192
+ ...typeof rest.state === "function" ? rest.state() : rest.state ?? {},
193
+ pressed: isSelected()
194
+ }),
190
195
  ...onPress ? { onPress } : {},
191
196
  ...disabled ? { disabled: true } : {},
192
197
  style: mergeStyle(base, style)
@@ -218,10 +223,10 @@ const Switch = (props) => {
218
223
  return createElement(Pressable, {
219
224
  ...rest,
220
225
  role: rest.role ?? "switch",
221
- state: {
222
- ...rest.state ?? {},
226
+ state: () => ({
227
+ ...typeof rest.state === "function" ? rest.state() : rest.state ?? {},
223
228
  checked: isOn()
224
- },
229
+ }),
225
230
  ...disabled ? { disabled: true } : {},
226
231
  ...handlePress ? { onPress: handlePress } : {},
227
232
  style: mergeStyle(track, style)
@@ -274,10 +279,30 @@ const ProgressBar = (props) => {
274
279
  return createElement(View, {
275
280
  ...rest,
276
281
  role: rest.role ?? "progressbar",
282
+ valueMin: 0,
283
+ valueMax: 1,
284
+ valueNow: () => Math.max(0, Math.min(1, progress())),
277
285
  style: mergeStyle(track, style)
278
286
  }, createElement(View, { style: fill }));
279
287
  };
288
+ const ActivityIndicator = (props) => {
289
+ const theme = useTheme();
290
+ const { size = 24, color, animating = true, style, ...rest } = props;
291
+ if (animating === false) return null;
292
+ const base = () => ({
293
+ width: size,
294
+ height: size,
295
+ color: color ?? theme().color.primary
296
+ });
297
+ const host = toHostProps({
298
+ ...rest,
299
+ style: mergeStyle(base, style)
300
+ });
301
+ if (!host.role) host.role = "status";
302
+ host["aria-busy"] = "true";
303
+ return createElement("activityindicator", host);
304
+ };
280
305
  //#endregion
281
- export { Avatar, Badge, Card, Chip, Divider, KeyboardAvoidingView, ProgressBar, SafeAreaView, Switch };
306
+ export { ActivityIndicator, Avatar, Badge, Card, Chip, Divider, KeyboardAvoidingView, ProgressBar, SafeAreaView, Switch };
282
307
 
283
308
  //# sourceMappingURL=components.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","names":["radiusScale","radius"],"sources":["../src/components.ts"],"sourcesContent":["/**\n * Atlas components — higher-level building blocks composed purely from the primitives\n * (View/Text/Pressable/Image) + the device hooks. No new host concepts, so every one\n * renders on web *and* native today, and stays fine-grained: reactive bits are accessor\n * styles, so only the changed node re-runs (no component re-render).\n *\n * Colors come from the design tokens via {@link useTheme}, so components re-theme\n * automatically light↔dark (handbook §23/§31). Spacing/radius/type use the token scales.\n *\n * @module\n */\n\nimport { type Accessor, type Component, createElement, type MindeesNode } from '@mindees/core'\nimport { useKeyboard, useSafeAreaInsets } from './environment'\nimport type { BaseProps, Reactive } from './host'\nimport { Image, Pressable, Text, View } from './primitives'\nimport { flattenStyle, type StyleInput } from './style'\nimport { fontWeight, radius as radiusScale, space, type Theme, useTheme } from './tokens'\n\n/** Merge a base style with a caller's (possibly reactive) style, staying reactive if either is. */\nfunction mergeStyle(\n base: StyleInput | Accessor<StyleInput>,\n style: Reactive<StyleInput> | undefined,\n): Reactive<StyleInput> {\n const baseFn = typeof base === 'function' ? (base as Accessor<StyleInput>) : null\n const styleFn = typeof style === 'function' ? (style as Accessor<StyleInput>) : null\n if (baseFn || styleFn) {\n // In each branch the non-fn side isn't a function, so the StyleInput cast is sound.\n const baseVal = base as StyleInput\n const styleVal = style as StyleInput\n return () => flattenStyle([baseFn ? baseFn() : baseVal, styleFn ? styleFn() : styleVal])\n }\n return flattenStyle([base as StyleInput, style as StyleInput])\n}\n\n/** Normalize a `Reactive<T>` to an accessor. */\nfunction toAccessor<T>(value: Reactive<T>, fallback: T): Accessor<T> {\n if (typeof value === 'function') return value as Accessor<T>\n return () => (value === undefined ? fallback : value)\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\n/** A surface that groups one coherent unit of content. */\nexport interface CardProps extends BaseProps {\n readonly children?: MindeesNode\n /** Visual emphasis. `elevated` (default) lifts off the bg; `filled` is a soft tint; `outlined` is a hairline. */\n readonly variant?: 'elevated' | 'filled' | 'outlined'\n /** Internal padding (handbook default 16). */\n readonly padding?: number | string\n /** Corner radius (handbook 12–16 for app cards). */\n readonly radius?: number\n}\nexport const Card: Component<CardProps> = (props) => {\n const theme = useTheme()\n const {\n variant = 'elevated',\n padding = space.md,\n radius = radiusScale.lg,\n style,\n children,\n ...rest\n } = props\n const base: Accessor<StyleInput> = () => {\n const c = theme().color\n const surface: StyleInput =\n variant === 'outlined'\n ? { borderWidth: 1, borderColor: c.border }\n : variant === 'filled'\n ? { backgroundColor: c.surfaceVariant }\n : { backgroundColor: c.surface, borderWidth: 1, borderColor: c.border }\n return { padding, borderRadius: radius, ...surface }\n }\n return createElement(View, { ...rest, style: mergeStyle(base, style) }, children)\n}\n\n// ---------------------------------------------------------------------------\n// Divider\n// ---------------------------------------------------------------------------\n\n/** A thin rule separating content. */\nexport interface DividerProps extends BaseProps {\n readonly orientation?: 'horizontal' | 'vertical'\n readonly thickness?: number\n /** Override color (defaults to the theme border). */\n readonly color?: string\n}\nexport const Divider: Component<DividerProps> = (props) => {\n const theme = useTheme()\n const { orientation = 'horizontal', thickness = 1, color, style, ...rest } = props\n const base: Accessor<StyleInput> = () => {\n const bg = color ?? theme().color.border\n return orientation === 'horizontal'\n ? { height: thickness, alignSelf: 'stretch', backgroundColor: bg }\n : { width: thickness, alignSelf: 'stretch', backgroundColor: bg }\n }\n return createElement(View, {\n ...rest,\n role: rest.role ?? 'separator',\n style: mergeStyle(base, style),\n })\n}\n\n// ---------------------------------------------------------------------------\n// Badge\n// ---------------------------------------------------------------------------\n\nexport type BadgeTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger'\n\n/** Resolve a tone to its {bg, fg} in the active theme. */\nfunction toneColors(tone: BadgeTone, theme: Theme): { bg: string; fg: string } {\n const c = theme.color\n if (tone === 'neutral') return { bg: c.surfaceVariant, fg: c.text }\n return { bg: c[tone], fg: c.onTone }\n}\n\n/** A compact status/count pill. */\nexport interface BadgeProps extends BaseProps {\n readonly children?: MindeesNode\n readonly tone?: BadgeTone\n}\nexport const Badge: Component<BadgeProps> = (props) => {\n const theme = useTheme()\n const { tone = 'neutral', style, children, ...rest } = props\n const base: Accessor<StyleInput> = () => ({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n paddingTop: space['3xs'],\n paddingBottom: space['3xs'],\n paddingLeft: space.xs,\n paddingRight: space.xs,\n borderRadius: radiusScale.full,\n backgroundColor: toneColors(tone, theme()).bg,\n })\n const textStyle: Accessor<StyleInput> = () => ({\n fontSize: 12,\n fontWeight: fontWeight.semibold,\n color: toneColors(tone, theme()).fg,\n })\n return createElement(\n View,\n { ...rest, role: rest.role ?? 'status', style: mergeStyle(base, style) },\n createElement(Text, { style: textStyle }, children),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Avatar\n// ---------------------------------------------------------------------------\n\n/** Up-to-two-letter initials from a name. */\nfunction initialsOf(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean)\n if (parts.length === 0) return '?'\n const first = parts[0]?.[0] ?? ''\n const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? '') : ''\n return (first + last).toUpperCase()\n}\n\n/** A circular user image, falling back to initials. */\nexport interface AvatarProps extends BaseProps {\n readonly src?: string\n readonly name?: string\n /** Diameter in px (default 40). */\n readonly size?: number\n}\nexport const Avatar: Component<AvatarProps> = (props) => {\n const theme = useTheme()\n const { src, name, size = 40, style, ...rest } = props\n const base: Accessor<StyleInput> = () => ({\n width: size,\n height: size,\n borderRadius: size / 2,\n overflow: 'hidden',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme().color.surfaceVariant,\n })\n const content = src\n ? createElement(Image, {\n src,\n label: name ?? '',\n ...(name ? {} : { decorative: true }),\n style: { width: size, height: size },\n })\n : createElement(\n Text,\n {\n style: () => ({\n fontSize: Math.round(size * 0.4),\n fontWeight: fontWeight.semibold,\n color: theme().color.text,\n }),\n },\n name ? initialsOf(name) : '?',\n )\n return createElement(\n View,\n { ...rest, label: rest.label ?? name, style: mergeStyle(base, style) },\n content,\n )\n}\n\n// ---------------------------------------------------------------------------\n// Chip\n// ---------------------------------------------------------------------------\n\n/** A compact, optionally-selectable token (filter/choice/input). */\nexport interface ChipProps extends Omit<BaseProps, 'style'> {\n readonly label: string\n readonly selected?: Reactive<boolean>\n readonly disabled?: boolean\n readonly onPress?: () => void\n readonly leading?: MindeesNode\n readonly trailing?: MindeesNode\n readonly style?: Reactive<StyleInput>\n}\nexport const Chip: Component<ChipProps> = (props) => {\n const theme = useTheme()\n const { label, selected = false, disabled, onPress, leading, trailing, style, ...rest } = props\n const isSelected = toAccessor(selected, false)\n const base: Accessor<StyleInput> = () => {\n const c = theme().color\n const on = isSelected()\n return {\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n gap: space['2xs'],\n minHeight: 32,\n paddingTop: space['2xs'],\n paddingBottom: space['2xs'],\n paddingLeft: space.sm,\n paddingRight: space.sm,\n borderRadius: radiusScale.full,\n borderWidth: 1,\n borderColor: on ? c.primary : c.border,\n backgroundColor: on ? c.primary : 'transparent',\n opacity: disabled ? 0.5 : 1,\n }\n }\n const text = createElement(\n Text,\n {\n style: () => ({\n fontSize: 14,\n fontWeight: fontWeight.medium,\n color: isSelected() ? theme().color.onPrimary : theme().color.text,\n }),\n },\n label,\n )\n const inner: MindeesNode = [leading, text, trailing].filter((n) => n != null) as MindeesNode\n return createElement(\n Pressable,\n {\n ...rest,\n role: rest.role ?? 'button',\n ...(onPress ? { onPress } : {}),\n ...(disabled ? { disabled: true } : {}),\n style: mergeStyle(base, style),\n },\n inner,\n )\n}\n\n// ---------------------------------------------------------------------------\n// Switch\n// ---------------------------------------------------------------------------\n\n/** A binary on/off toggle (composed track + knob; flips instantly on press). */\nexport interface SwitchProps extends Omit<BaseProps, 'style'> {\n /** Controlled state (static or reactive). */\n readonly value: Reactive<boolean>\n readonly onValueChange?: (value: boolean) => void\n readonly disabled?: boolean\n readonly style?: Reactive<StyleInput>\n}\nexport const Switch: Component<SwitchProps> = (props) => {\n const theme = useTheme()\n const { value, onValueChange, disabled, style, ...rest } = props\n const isOn = toAccessor(value, false)\n const track: Accessor<StyleInput> = () => ({\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: isOn() ? 'flex-end' : 'flex-start',\n width: 52,\n height: 32,\n borderRadius: radiusScale.full,\n padding: 3,\n backgroundColor: isOn() ? theme().color.primary : theme().color.textMuted,\n opacity: disabled ? 0.5 : 1,\n })\n const knob = createElement(View, {\n style: () => ({\n width: 26,\n height: 26,\n borderRadius: 13,\n backgroundColor: theme().color.onPrimary,\n }),\n })\n const handlePress = onValueChange && !disabled ? () => onValueChange(!isOn()) : undefined\n return createElement(\n Pressable,\n {\n ...rest,\n role: rest.role ?? 'switch',\n state: { ...(rest.state ?? {}), checked: isOn() },\n ...(disabled ? { disabled: true } : {}),\n ...(handlePress ? { onPress: handlePress } : {}),\n style: mergeStyle(track, style),\n },\n knob,\n )\n}\n\n// ---------------------------------------------------------------------------\n// SafeAreaView\n// ---------------------------------------------------------------------------\n\n/** A container that pads itself by the live safe-area insets (notch, home indicator, …). */\nexport interface SafeAreaViewProps extends BaseProps {\n readonly children?: MindeesNode\n /** Which edges to inset (default: all four). */\n readonly edges?: ReadonlyArray<'top' | 'right' | 'bottom' | 'left'>\n}\nexport const SafeAreaView: Component<SafeAreaViewProps> = (props) => {\n const insets = useSafeAreaInsets()\n const { edges, style, children, ...rest } = props\n const wants = (edge: 'top' | 'right' | 'bottom' | 'left'): boolean =>\n !edges || edges.includes(edge)\n const base: Accessor<StyleInput> = () => {\n const i = insets()\n return {\n paddingTop: wants('top') ? i.top : 0,\n paddingRight: wants('right') ? i.right : 0,\n paddingBottom: wants('bottom') ? i.bottom : 0,\n paddingLeft: wants('left') ? i.left : 0,\n }\n }\n return createElement(View, { ...rest, style: mergeStyle(base, style) }, children)\n}\n\n// ---------------------------------------------------------------------------\n// KeyboardAvoidingView\n// ---------------------------------------------------------------------------\n\n/** A container that pads its bottom by the live keyboard height so content stays visible. */\nexport interface KeyboardAvoidingViewProps extends BaseProps {\n readonly children?: MindeesNode\n}\nexport const KeyboardAvoidingView: Component<KeyboardAvoidingViewProps> = (props) => {\n const keyboard = useKeyboard()\n const { style, children, ...rest } = props\n const base: Accessor<StyleInput> = () => ({ paddingBottom: keyboard().height })\n return createElement(View, { ...rest, style: mergeStyle(base, style) }, children)\n}\n\n// ---------------------------------------------------------------------------\n// ProgressBar (determinate)\n// ---------------------------------------------------------------------------\n\n/** A determinate progress bar (track + reactive fill). */\nexport interface ProgressBarProps extends BaseProps {\n /** Progress 0..1 (static or reactive). Values outside the range are clamped. */\n readonly value?: Reactive<number>\n readonly trackColor?: string\n readonly color?: string\n readonly height?: number\n}\nexport const ProgressBar: Component<ProgressBarProps> = (props) => {\n const theme = useTheme()\n const { value = 0, trackColor, color, height = 6, style, ...rest } = props\n const progress = toAccessor(value, 0)\n const track: Accessor<StyleInput> = () => ({\n width: '100%',\n height,\n borderRadius: height / 2,\n overflow: 'hidden',\n backgroundColor: trackColor ?? theme().color.surfaceVariant,\n })\n const fill: Accessor<StyleInput> = () => ({\n height,\n borderRadius: height / 2,\n backgroundColor: color ?? theme().color.primary,\n width: `${Math.max(0, Math.min(1, progress())) * 100}%`,\n })\n return createElement(\n View,\n { ...rest, role: rest.role ?? 'progressbar', style: mergeStyle(track, style) },\n createElement(View, { style: fill }),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"components.js","names":["radiusScale","radius"],"sources":["../src/components.ts"],"sourcesContent":["/**\n * Atlas components — higher-level building blocks composed purely from the primitives\n * (View/Text/Pressable/Image) + the device hooks. No new host concepts, so every one\n * renders on web *and* native today, and stays fine-grained: reactive bits are accessor\n * styles, so only the changed node re-runs (no component re-render).\n *\n * Colors come from the design tokens via {@link useTheme}, so components re-theme\n * automatically light↔dark (handbook §23/§31). Spacing/radius/type use the token scales.\n *\n * @module\n */\n\nimport { type Accessor, type Component, createElement, type MindeesNode } from '@mindees/core'\nimport { useKeyboard, useSafeAreaInsets } from './environment'\nimport { type BaseProps, type Reactive, toHostProps } from './host'\nimport { Image, Pressable, Text, View } from './primitives'\nimport { flattenStyle, type StyleInput } from './style'\nimport { fontWeight, radius as radiusScale, space, type Theme, useTheme } from './tokens'\n\n/** Merge a base style with a caller's (possibly reactive) style, staying reactive if either is. */\nfunction mergeStyle(\n base: StyleInput | Accessor<StyleInput>,\n style: Reactive<StyleInput> | undefined,\n): Reactive<StyleInput> {\n const baseFn = typeof base === 'function' ? (base as Accessor<StyleInput>) : null\n const styleFn = typeof style === 'function' ? (style as Accessor<StyleInput>) : null\n if (baseFn || styleFn) {\n // In each branch the non-fn side isn't a function, so the StyleInput cast is sound.\n const baseVal = base as StyleInput\n const styleVal = style as StyleInput\n return () => flattenStyle([baseFn ? baseFn() : baseVal, styleFn ? styleFn() : styleVal])\n }\n return flattenStyle([base as StyleInput, style as StyleInput])\n}\n\n/** Normalize a `Reactive<T>` to an accessor. */\nfunction toAccessor<T>(value: Reactive<T>, fallback: T): Accessor<T> {\n if (typeof value === 'function') return value as Accessor<T>\n return () => (value === undefined ? fallback : value)\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\n/** A surface that groups one coherent unit of content. */\nexport interface CardProps extends BaseProps {\n readonly children?: MindeesNode\n /** Visual emphasis. `elevated` (default) lifts off the bg; `filled` is a soft tint; `outlined` is a hairline. */\n readonly variant?: 'elevated' | 'filled' | 'outlined'\n /** Internal padding (handbook default 16). */\n readonly padding?: number | string\n /** Corner radius (handbook 12–16 for app cards). */\n readonly radius?: number\n}\nexport const Card: Component<CardProps> = (props) => {\n const theme = useTheme()\n const {\n variant = 'elevated',\n padding = space.md,\n radius = radiusScale.lg,\n style,\n children,\n ...rest\n } = props\n const base: Accessor<StyleInput> = () => {\n const c = theme().color\n const surface: StyleInput =\n variant === 'outlined'\n ? { borderWidth: 1, borderColor: c.border }\n : variant === 'filled'\n ? { backgroundColor: c.surfaceVariant }\n : { backgroundColor: c.surface, borderWidth: 1, borderColor: c.border }\n return { padding, borderRadius: radius, ...surface }\n }\n return createElement(View, { ...rest, style: mergeStyle(base, style) }, children)\n}\n\n// ---------------------------------------------------------------------------\n// Divider\n// ---------------------------------------------------------------------------\n\n/** A thin rule separating content. */\nexport interface DividerProps extends BaseProps {\n readonly orientation?: 'horizontal' | 'vertical'\n readonly thickness?: number\n /** Override color (defaults to the theme border). */\n readonly color?: string\n}\nexport const Divider: Component<DividerProps> = (props) => {\n const theme = useTheme()\n const { orientation = 'horizontal', thickness = 1, color, style, ...rest } = props\n const base: Accessor<StyleInput> = () => {\n const bg = color ?? theme().color.border\n return orientation === 'horizontal'\n ? { height: thickness, alignSelf: 'stretch', backgroundColor: bg }\n : { width: thickness, alignSelf: 'stretch', backgroundColor: bg }\n }\n return createElement(View, {\n ...rest,\n role: rest.role ?? 'separator',\n style: mergeStyle(base, style),\n })\n}\n\n// ---------------------------------------------------------------------------\n// Badge\n// ---------------------------------------------------------------------------\n\nexport type BadgeTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger'\n\n/** Resolve a tone to its {bg, fg} in the active theme. */\nfunction toneColors(tone: BadgeTone, theme: Theme): { bg: string; fg: string } {\n const c = theme.color\n if (tone === 'neutral') return { bg: c.surfaceVariant, fg: c.text }\n return { bg: c[tone], fg: c.onTone }\n}\n\n/** A compact status/count pill. */\nexport interface BadgeProps extends BaseProps {\n readonly children?: MindeesNode\n readonly tone?: BadgeTone\n}\nexport const Badge: Component<BadgeProps> = (props) => {\n const theme = useTheme()\n const { tone = 'neutral', style, children, ...rest } = props\n const base: Accessor<StyleInput> = () => ({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n paddingTop: space['3xs'],\n paddingBottom: space['3xs'],\n paddingLeft: space.xs,\n paddingRight: space.xs,\n borderRadius: radiusScale.full,\n backgroundColor: toneColors(tone, theme()).bg,\n })\n const textStyle: Accessor<StyleInput> = () => ({\n fontSize: 12,\n fontWeight: fontWeight.semibold,\n color: toneColors(tone, theme()).fg,\n })\n return createElement(\n View,\n { ...rest, role: rest.role ?? 'status', style: mergeStyle(base, style) },\n createElement(Text, { style: textStyle }, children),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Avatar\n// ---------------------------------------------------------------------------\n\n/** Up-to-two-letter initials from a name. */\nfunction initialsOf(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean)\n if (parts.length === 0) return '?'\n const first = parts[0]?.[0] ?? ''\n const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? '') : ''\n return (first + last).toUpperCase()\n}\n\n/** A circular user image, falling back to initials. */\nexport interface AvatarProps extends BaseProps {\n readonly src?: string\n readonly name?: string\n /** Diameter in px (default 40). */\n readonly size?: number\n}\nexport const Avatar: Component<AvatarProps> = (props) => {\n const theme = useTheme()\n const { src, name, size = 40, style, ...rest } = props\n const base: Accessor<StyleInput> = () => ({\n width: size,\n height: size,\n borderRadius: size / 2,\n overflow: 'hidden',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme().color.surfaceVariant,\n })\n const content = src\n ? createElement(Image, {\n src,\n label: name ?? '',\n ...(name ? {} : { decorative: true }),\n style: { width: size, height: size },\n })\n : createElement(\n Text,\n {\n style: () => ({\n fontSize: Math.round(size * 0.4),\n fontWeight: fontWeight.semibold,\n color: theme().color.text,\n }),\n },\n name ? initialsOf(name) : '?',\n )\n return createElement(\n View,\n { ...rest, label: rest.label ?? name, style: mergeStyle(base, style) },\n content,\n )\n}\n\n// ---------------------------------------------------------------------------\n// Chip\n// ---------------------------------------------------------------------------\n\n/** A compact, optionally-selectable token (filter/choice/input). */\nexport interface ChipProps extends Omit<BaseProps, 'style'> {\n readonly label: string\n readonly selected?: Reactive<boolean>\n readonly disabled?: boolean\n readonly onPress?: () => void\n readonly leading?: MindeesNode\n readonly trailing?: MindeesNode\n readonly style?: Reactive<StyleInput>\n}\nexport const Chip: Component<ChipProps> = (props) => {\n const theme = useTheme()\n const { label, selected = false, disabled, onPress, leading, trailing, style, ...rest } = props\n const isSelected = toAccessor(selected, false)\n const base: Accessor<StyleInput> = () => {\n const c = theme().color\n const on = isSelected()\n return {\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n gap: space['2xs'],\n minHeight: 32,\n paddingTop: space['2xs'],\n paddingBottom: space['2xs'],\n paddingLeft: space.sm,\n paddingRight: space.sm,\n borderRadius: radiusScale.full,\n borderWidth: 1,\n borderColor: on ? c.primary : c.border,\n backgroundColor: on ? c.primary : 'transparent',\n opacity: disabled ? 0.5 : 1,\n }\n }\n const text = createElement(\n Text,\n {\n style: () => ({\n fontSize: 14,\n fontWeight: fontWeight.medium,\n color: isSelected() ? theme().color.onPrimary : theme().color.text,\n }),\n },\n label,\n )\n const inner: MindeesNode = [leading, text, trailing].filter((n) => n != null) as MindeesNode\n return createElement(\n Pressable,\n {\n ...rest,\n role: rest.role ?? 'button',\n // Reactive `aria-pressed` so a toggle chip announces its selected state as it changes.\n state: () => ({\n ...(typeof rest.state === 'function' ? rest.state() : (rest.state ?? {})),\n pressed: isSelected(),\n }),\n ...(onPress ? { onPress } : {}),\n ...(disabled ? { disabled: true } : {}),\n style: mergeStyle(base, style),\n },\n inner,\n )\n}\n\n// ---------------------------------------------------------------------------\n// Switch\n// ---------------------------------------------------------------------------\n\n/** A binary on/off toggle (composed track + knob; flips instantly on press). */\nexport interface SwitchProps extends Omit<BaseProps, 'style'> {\n /** Controlled state (static or reactive). */\n readonly value: Reactive<boolean>\n readonly onValueChange?: (value: boolean) => void\n readonly disabled?: boolean\n readonly style?: Reactive<StyleInput>\n}\nexport const Switch: Component<SwitchProps> = (props) => {\n const theme = useTheme()\n const { value, onValueChange, disabled, style, ...rest } = props\n const isOn = toAccessor(value, false)\n const track: Accessor<StyleInput> = () => ({\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: isOn() ? 'flex-end' : 'flex-start',\n width: 52,\n height: 32,\n borderRadius: radiusScale.full,\n padding: 3,\n backgroundColor: isOn() ? theme().color.primary : theme().color.textMuted,\n opacity: disabled ? 0.5 : 1,\n })\n const knob = createElement(View, {\n style: () => ({\n width: 26,\n height: 26,\n borderRadius: 13,\n backgroundColor: theme().color.onPrimary,\n }),\n })\n const handlePress = onValueChange && !disabled ? () => onValueChange(!isOn()) : undefined\n return createElement(\n Pressable,\n {\n ...rest,\n role: rest.role ?? 'switch',\n // Reactive state → `aria-checked` tracks the toggle (a static object would bake it once).\n state: () => ({\n ...(typeof rest.state === 'function' ? rest.state() : (rest.state ?? {})),\n checked: isOn(),\n }),\n ...(disabled ? { disabled: true } : {}),\n ...(handlePress ? { onPress: handlePress } : {}),\n style: mergeStyle(track, style),\n },\n knob,\n )\n}\n\n// ---------------------------------------------------------------------------\n// SafeAreaView\n// ---------------------------------------------------------------------------\n\n/** A container that pads itself by the live safe-area insets (notch, home indicator, …). */\nexport interface SafeAreaViewProps extends BaseProps {\n readonly children?: MindeesNode\n /** Which edges to inset (default: all four). */\n readonly edges?: ReadonlyArray<'top' | 'right' | 'bottom' | 'left'>\n}\nexport const SafeAreaView: Component<SafeAreaViewProps> = (props) => {\n const insets = useSafeAreaInsets()\n const { edges, style, children, ...rest } = props\n const wants = (edge: 'top' | 'right' | 'bottom' | 'left'): boolean =>\n !edges || edges.includes(edge)\n const base: Accessor<StyleInput> = () => {\n const i = insets()\n return {\n paddingTop: wants('top') ? i.top : 0,\n paddingRight: wants('right') ? i.right : 0,\n paddingBottom: wants('bottom') ? i.bottom : 0,\n paddingLeft: wants('left') ? i.left : 0,\n }\n }\n return createElement(View, { ...rest, style: mergeStyle(base, style) }, children)\n}\n\n// ---------------------------------------------------------------------------\n// KeyboardAvoidingView\n// ---------------------------------------------------------------------------\n\n/** A container that pads its bottom by the live keyboard height so content stays visible. */\nexport interface KeyboardAvoidingViewProps extends BaseProps {\n readonly children?: MindeesNode\n}\nexport const KeyboardAvoidingView: Component<KeyboardAvoidingViewProps> = (props) => {\n const keyboard = useKeyboard()\n const { style, children, ...rest } = props\n const base: Accessor<StyleInput> = () => ({ paddingBottom: keyboard().height })\n return createElement(View, { ...rest, style: mergeStyle(base, style) }, children)\n}\n\n// ---------------------------------------------------------------------------\n// ProgressBar (determinate)\n// ---------------------------------------------------------------------------\n\n/** A determinate progress bar (track + reactive fill). */\nexport interface ProgressBarProps extends BaseProps {\n /** Progress 0..1 (static or reactive). Values outside the range are clamped. */\n readonly value?: Reactive<number>\n readonly trackColor?: string\n readonly color?: string\n readonly height?: number\n}\nexport const ProgressBar: Component<ProgressBarProps> = (props) => {\n const theme = useTheme()\n const { value = 0, trackColor, color, height = 6, style, ...rest } = props\n const progress = toAccessor(value, 0)\n const track: Accessor<StyleInput> = () => ({\n width: '100%',\n height,\n borderRadius: height / 2,\n overflow: 'hidden',\n backgroundColor: trackColor ?? theme().color.surfaceVariant,\n })\n const fill: Accessor<StyleInput> = () => ({\n height,\n borderRadius: height / 2,\n backgroundColor: color ?? theme().color.primary,\n width: `${Math.max(0, Math.min(1, progress())) * 100}%`,\n })\n return createElement(\n View,\n {\n ...rest,\n role: rest.role ?? 'progressbar',\n // A progressbar with no value is inaccessible — emit a reactive aria-valuenow (0..1 range).\n valueMin: 0,\n valueMax: 1,\n valueNow: () => Math.max(0, Math.min(1, progress())),\n style: mergeStyle(track, style),\n },\n createElement(View, { style: fill }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// ActivityIndicator\n// ---------------------------------------------------------------------------\n\n/**\n * A spinning loading indicator. Emits the `activityindicator` host tag, which each backend\n * renders natively: web → a CSS keyframe spinner (size from `width`/`height`, arc from `color`),\n * Android → an indeterminate `ProgressBar`. Size/color flow through ordinary style keys.\n */\nexport interface ActivityIndicatorProps extends BaseProps {\n /** Diameter in px (default 24). */\n readonly size?: number\n /** Spinner color (defaults to the theme primary). */\n readonly color?: string\n /** When false, renders nothing (so callers can gate it without a conditional). */\n readonly animating?: boolean\n}\nexport const ActivityIndicator: Component<ActivityIndicatorProps> = (props) => {\n const theme = useTheme()\n const { size = 24, color, animating = true, style, ...rest } = props\n if (animating === false) return null\n const base: Accessor<StyleInput> = () => ({\n width: size,\n height: size,\n color: color ?? theme().color.primary,\n })\n const host = toHostProps({ ...rest, style: mergeStyle(base, style) })\n if (!host.role) host.role = 'status'\n host['aria-busy'] = 'true'\n return createElement('activityindicator', host)\n}\n"],"mappings":"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"}
package/dist/for.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { KeyedRegion, MindeesNode } from "@mindees/core";
2
+
3
+ //#region src/for.d.ts
4
+ /** Props for {@link For}. */
5
+ interface ForProps<T> {
6
+ /** The items, static or reactive. */
7
+ readonly each: readonly T[] | (() => readonly T[]);
8
+ /** Render one row from reactive `item`/`index` accessors. */
9
+ readonly children: (item: () => T, index: () => number) => MindeesNode;
10
+ /** Stable key per item (defaults to item identity). Provide it for primitive/object lists that change shape. */
11
+ readonly key?: (item: T, index: number) => unknown;
12
+ /** Rendered when the list is empty. */
13
+ readonly fallback?: () => MindeesNode;
14
+ }
15
+ /** A keyed, identity-preserving list. Returns a {@link KeyedRegion} node the renderer reconciles. */
16
+ declare function For<T>(props: ForProps<T>): KeyedRegion<T>;
17
+ //#endregion
18
+ export { For, ForProps };
19
+ //# sourceMappingURL=for.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"for.d.ts","names":[],"sources":["../src/for.ts"],"mappings":";;;;UAoBiB,QAAA;EAIiB;EAAA,SAFvB,IAAA,WAAe,CAAA,qBAAsB,CAAA;EAEX;EAAA,SAA1B,QAAA,GAAW,IAAA,QAAY,CAAA,EAAG,KAAA,mBAAwB,WAAA;EAElD;EAAA,SAAA,GAAA,IAAO,IAAA,EAAM,CAAA,EAAG,KAAA;EAAT;EAAA,SAEP,QAAA,SAAiB,WAAA;AAAA;;iBAIZ,GAAA,IAAO,KAAA,EAAO,QAAA,CAAS,CAAA,IAAK,WAAA,CAAY,CAAA"}
package/dist/for.js ADDED
@@ -0,0 +1,26 @@
1
+ import { keyedRegion } from "@mindees/core";
2
+ //#region src/for.ts
3
+ /**
4
+ * Atlas `For` — the ergonomic, keyed list component (on the `@mindees/atlas/for` subpath).
5
+ *
6
+ * `For` is keyed identity (rows keep their host node, focus, scroll across reorders); the
7
+ * virtualized {@link import('./list').List} is for huge lists — complementary, not competing.
8
+ * Use `For` when you'd otherwise write `() => items().map(...)` (which full-rebuilds): it
9
+ * reconciles by key so only the diff is created/moved/disposed. Consume the `item`/`index`
10
+ * accessors lazily so a row patches in place.
11
+ *
12
+ * @example
13
+ * For({ each: () => todos(), key: (t) => t.id, children: (todo) => (
14
+ * <Row><Text>{() => todo().title}</Text></Row>
15
+ * )})
16
+ *
17
+ * @module
18
+ */
19
+ /** A keyed, identity-preserving list. Returns a {@link KeyedRegion} node the renderer reconciles. */
20
+ function For(props) {
21
+ return keyedRegion(props);
22
+ }
23
+ //#endregion
24
+ export { For };
25
+
26
+ //# sourceMappingURL=for.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"for.js","names":[],"sources":["../src/for.ts"],"sourcesContent":["/**\n * Atlas `For` — the ergonomic, keyed list component (on the `@mindees/atlas/for` subpath).\n *\n * `For` is keyed identity (rows keep their host node, focus, scroll across reorders); the\n * virtualized {@link import('./list').List} is for huge lists — complementary, not competing.\n * Use `For` when you'd otherwise write `() => items().map(...)` (which full-rebuilds): it\n * reconciles by key so only the diff is created/moved/disposed. Consume the `item`/`index`\n * accessors lazily so a row patches in place.\n *\n * @example\n * For({ each: () => todos(), key: (t) => t.id, children: (todo) => (\n * <Row><Text>{() => todo().title}</Text></Row>\n * )})\n *\n * @module\n */\n\nimport { type KeyedRegion, keyedRegion, type MindeesNode } from '@mindees/core'\n\n/** Props for {@link For}. */\nexport interface ForProps<T> {\n /** The items, static or reactive. */\n readonly each: readonly T[] | (() => readonly T[])\n /** Render one row from reactive `item`/`index` accessors. */\n readonly children: (item: () => T, index: () => number) => MindeesNode\n /** Stable key per item (defaults to item identity). Provide it for primitive/object lists that change shape. */\n readonly key?: (item: T, index: number) => unknown\n /** Rendered when the list is empty. */\n readonly fallback?: () => MindeesNode\n}\n\n/** A keyed, identity-preserving list. Returns a {@link KeyedRegion} node the renderer reconciles. */\nexport function For<T>(props: ForProps<T>): KeyedRegion<T> {\n return keyedRegion<T>(props)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,IAAO,OAAoC;CACzD,OAAO,YAAe,KAAK;AAC7B"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { A11yProps, A11yState, Role, toA11yProps } from "./a11y.js";
2
2
  import { StyleInput, StyleObject, StyleValue, flattenStyle } from "./style.js";
3
3
  import { BaseProps, Reactive, resolveStyle, toHostProps } from "./host.js";
4
- import { Avatar, AvatarProps, Badge, BadgeProps, Card, CardProps, Chip, ChipProps, Divider, DividerProps, KeyboardAvoidingView, KeyboardAvoidingViewProps, ProgressBar, ProgressBarProps, SafeAreaView, SafeAreaViewProps, Switch, SwitchProps } from "./components.js";
4
+ import { ActivityIndicator, ActivityIndicatorProps, Avatar, AvatarProps, Badge, BadgeProps, Card, CardProps, Chip, ChipProps, Divider, DividerProps, KeyboardAvoidingView, KeyboardAvoidingViewProps, ProgressBar, ProgressBarProps, SafeAreaView, SafeAreaViewProps, Switch, SwitchProps } from "./components.js";
5
5
  import { ColorScheme, KeyboardState, PlatformEnvironment, SafeAreaInsets, WindowDimensions, getEnvironment, setEnvironment, useColorScheme, useKeyboard, useSafeAreaInsets, useWindowDimensions } from "./environment.js";
6
+ import { FocusScope, FocusScopeProps, Modal, ModalProps } from "./overlay.js";
6
7
  import { Button, ButtonProps, Column, Image, ImageProps, InteractionState, Pressable, PressableProps, Row, ScrollView, ScrollViewProps, Spacer, SpacerProps, Stack, StackProps, Text, TextInput, TextInputProps, TextProps, View, ViewProps, usePressable } from "./primitives.js";
7
8
  import { Theme, ThemeColors, duration, easing, fontSize, fontWeight, getTheme, lineHeight, palette, radius, space, tokens, useTheme } from "./tokens.js";
8
9
  import { Maturity, NotImplementedError, PackageInfo, notImplemented } from "@mindees/core";
@@ -11,7 +12,7 @@ import { Maturity, NotImplementedError, PackageInfo, notImplemented } from "@min
11
12
  /** The npm package name. */
12
13
  declare const name = "@mindees/atlas";
13
14
  /** The package version. All `@mindees/*` packages share one locked version line. */
14
- declare const VERSION = "0.2.0";
15
+ declare const VERSION = "0.4.0";
15
16
  /** Current maturity of this package. See the repository `STATUS.md`. */
16
17
  declare const maturity: Maturity;
17
18
  /**
@@ -21,5 +22,5 @@ declare const maturity: Maturity;
21
22
  */
22
23
  declare const info: PackageInfo;
23
24
  //#endregion
24
- export { type A11yProps, type A11yState, Avatar, type AvatarProps, Badge, type BadgeProps, type BaseProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, type ColorScheme, Column, Divider, type DividerProps, Image, type ImageProps, type InteractionState, KeyboardAvoidingView, type KeyboardAvoidingViewProps, type KeyboardState, type Maturity, NotImplementedError, type PackageInfo, type PlatformEnvironment, Pressable, type PressableProps, ProgressBar, type ProgressBarProps, type Reactive, type Role, Row, type SafeAreaInsets, SafeAreaView, type SafeAreaViewProps, ScrollView, type ScrollViewProps, Spacer, type SpacerProps, Stack, type StackProps, type StyleInput, type StyleObject, type StyleValue, Switch, type SwitchProps, Text, TextInput, type TextInputProps, type TextProps, type Theme, type ThemeColors, VERSION, View, type ViewProps, type WindowDimensions, duration, easing, flattenStyle, fontSize, fontWeight, getEnvironment, getTheme, info, lineHeight, maturity, name, notImplemented, palette, radius, resolveStyle, setEnvironment, space, toA11yProps, toHostProps, tokens, useColorScheme, useKeyboard, usePressable, useSafeAreaInsets, useTheme, useWindowDimensions };
25
+ export { type A11yProps, type A11yState, ActivityIndicator, type ActivityIndicatorProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BaseProps, Button, type ButtonProps, Card, type CardProps, Chip, type ChipProps, type ColorScheme, Column, Divider, type DividerProps, FocusScope, type FocusScopeProps, Image, type ImageProps, type InteractionState, KeyboardAvoidingView, type KeyboardAvoidingViewProps, type KeyboardState, type Maturity, Modal, type ModalProps, NotImplementedError, type PackageInfo, type PlatformEnvironment, Pressable, type PressableProps, ProgressBar, type ProgressBarProps, type Reactive, type Role, Row, type SafeAreaInsets, SafeAreaView, type SafeAreaViewProps, ScrollView, type ScrollViewProps, Spacer, type SpacerProps, Stack, type StackProps, type StyleInput, type StyleObject, type StyleValue, Switch, type SwitchProps, Text, TextInput, type TextInputProps, type TextProps, type Theme, type ThemeColors, VERSION, View, type ViewProps, type WindowDimensions, duration, easing, flattenStyle, fontSize, fontWeight, getEnvironment, getTheme, info, lineHeight, maturity, name, notImplemented, palette, radius, resolveStyle, setEnvironment, space, toA11yProps, toHostProps, tokens, useColorScheme, useKeyboard, usePressable, useSafeAreaInsets, useTheme, useWindowDimensions };
25
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;cAea,IAAA;AAAI;AAAA,cAGJ,OAAA;;cAGA,QAAA,EAAU,QAAyB;;AAH5B;AAGpB;;;cAOa,IAAA,EAAM,WAAiE"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;cAea,IAAA;;cAGA,OAAA;AAAb;AAAA,cAGa,QAAA,EAAU,QAAyB;;;AAH5B;AAGpB;;cAOa,IAAA,EAAM,WAAiE"}
package/dist/index.js CHANGED
@@ -4,13 +4,14 @@ import { flattenStyle } from "./style.js";
4
4
  import { resolveStyle, toHostProps } from "./host.js";
5
5
  import { Button, Column, Image, Pressable, Row, ScrollView, Spacer, Stack, Text, TextInput, View, usePressable } from "./primitives.js";
6
6
  import { duration, easing, fontSize, fontWeight, getTheme, lineHeight, palette, radius, space, tokens, useTheme } from "./tokens.js";
7
- import { Avatar, Badge, Card, Chip, Divider, KeyboardAvoidingView, ProgressBar, SafeAreaView, Switch } from "./components.js";
7
+ import { ActivityIndicator, Avatar, Badge, Card, Chip, Divider, KeyboardAvoidingView, ProgressBar, SafeAreaView, Switch } from "./components.js";
8
+ import { FocusScope, Modal } from "./overlay.js";
8
9
  import { NotImplementedError, notImplemented } from "@mindees/core";
9
10
  //#region src/index.ts
10
11
  /** The npm package name. */
11
12
  const name = "@mindees/atlas";
12
13
  /** The package version. All `@mindees/*` packages share one locked version line. */
13
- const VERSION = "0.2.0";
14
+ const VERSION = "0.4.0";
14
15
  /** Current maturity of this package. See the repository `STATUS.md`. */
15
16
  const maturity = "experimental";
16
17
  /**
@@ -24,6 +25,6 @@ const info = Object.freeze({
24
25
  maturity
25
26
  });
26
27
  //#endregion
27
- export { Avatar, Badge, Button, Card, Chip, Column, Divider, Image, KeyboardAvoidingView, NotImplementedError, Pressable, ProgressBar, Row, SafeAreaView, ScrollView, Spacer, Stack, Switch, Text, TextInput, VERSION, View, duration, easing, flattenStyle, fontSize, fontWeight, getEnvironment, getTheme, info, lineHeight, maturity, name, notImplemented, palette, radius, resolveStyle, setEnvironment, space, toA11yProps, toHostProps, tokens, useColorScheme, useKeyboard, usePressable, useSafeAreaInsets, useTheme, useWindowDimensions };
28
+ export { ActivityIndicator, Avatar, Badge, Button, Card, Chip, Column, Divider, FocusScope, Image, KeyboardAvoidingView, Modal, NotImplementedError, Pressable, ProgressBar, Row, SafeAreaView, ScrollView, Spacer, Stack, Switch, Text, TextInput, VERSION, View, duration, easing, flattenStyle, fontSize, fontWeight, getEnvironment, getTheme, info, lineHeight, maturity, name, notImplemented, palette, radius, resolveStyle, setEnvironment, space, toA11yProps, toHostProps, tokens, useColorScheme, useKeyboard, usePressable, useSafeAreaInsets, useTheme, useWindowDimensions };
28
29
 
29
30
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@mindees/atlas` (Atlas) — accessible, signals-native UI primitives. Function components\n * over `@mindees/core`'s `createElement` that return renderer-agnostic `MindeesNode` trees:\n * web rendering is real via the Helix DOM backend; native is a labeled 🔬 research track (the\n * same serializable tree, interpreted by a native host later). A curated cross-platform\n * `StyleObject`, typed accessibility, and a structural theme (on the `@mindees/atlas/theme`\n * subpath). The virtualized recycling `List` is on the `@mindees/atlas/list` subpath.\n *\n * @module\n */\n\nimport type { Maturity, PackageInfo } from '@mindees/core'\nimport { NotImplementedError, notImplemented } from '@mindees/core'\n\n/** The npm package name. */\nexport const name = '@mindees/atlas'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.2.0'\n\n/** Current maturity of this package. See the repository `STATUS.md`. */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n\nexport { type A11yProps, type A11yState, type Role, toA11yProps } from './a11y'\nexport {\n Avatar,\n type AvatarProps,\n Badge,\n type BadgeProps,\n Card,\n type CardProps,\n Chip,\n type ChipProps,\n Divider,\n type DividerProps,\n KeyboardAvoidingView,\n type KeyboardAvoidingViewProps,\n ProgressBar,\n type ProgressBarProps,\n SafeAreaView,\n type SafeAreaViewProps,\n Switch,\n type SwitchProps,\n} from './components'\nexport {\n type ColorScheme,\n getEnvironment,\n type KeyboardState,\n type PlatformEnvironment,\n type SafeAreaInsets,\n setEnvironment,\n useColorScheme,\n useKeyboard,\n useSafeAreaInsets,\n useWindowDimensions,\n type WindowDimensions,\n} from './environment'\nexport { type BaseProps, type Reactive, resolveStyle, toHostProps } from './host'\nexport {\n Button,\n type ButtonProps,\n Column,\n Image,\n type ImageProps,\n type InteractionState,\n Pressable,\n type PressableProps,\n Row,\n ScrollView,\n type ScrollViewProps,\n Spacer,\n type SpacerProps,\n Stack,\n type StackProps,\n Text,\n TextInput,\n type TextInputProps,\n type TextProps,\n usePressable,\n View,\n type ViewProps,\n} from './primitives'\nexport { flattenStyle, type StyleInput, type StyleObject, type StyleValue } from './style'\nexport {\n duration,\n easing,\n fontSize,\n fontWeight,\n getTheme,\n lineHeight,\n palette,\n radius,\n space,\n type Theme,\n type ThemeColors,\n tokens,\n useTheme,\n} from './tokens'\nexport type { Maturity, PackageInfo }\nexport { NotImplementedError, notImplemented }\n"],"mappings":";;;;;;;;;;AAeA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;AAGvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@mindees/atlas` (Atlas) — accessible, signals-native UI primitives. Function components\n * over `@mindees/core`'s `createElement` that return renderer-agnostic `MindeesNode` trees:\n * web rendering is real via the Helix DOM backend; native is a labeled 🔬 research track (the\n * same serializable tree, interpreted by a native host later). A curated cross-platform\n * `StyleObject`, typed accessibility, and a structural theme (on the `@mindees/atlas/theme`\n * subpath). The virtualized recycling `List` is on the `@mindees/atlas/list` subpath.\n *\n * @module\n */\n\nimport type { Maturity, PackageInfo } from '@mindees/core'\nimport { NotImplementedError, notImplemented } from '@mindees/core'\n\n/** The npm package name. */\nexport const name = '@mindees/atlas'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.4.0'\n\n/** Current maturity of this package. See the repository `STATUS.md`. */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n\nexport { type A11yProps, type A11yState, type Role, toA11yProps } from './a11y'\nexport {\n ActivityIndicator,\n type ActivityIndicatorProps,\n Avatar,\n type AvatarProps,\n Badge,\n type BadgeProps,\n Card,\n type CardProps,\n Chip,\n type ChipProps,\n Divider,\n type DividerProps,\n KeyboardAvoidingView,\n type KeyboardAvoidingViewProps,\n ProgressBar,\n type ProgressBarProps,\n SafeAreaView,\n type SafeAreaViewProps,\n Switch,\n type SwitchProps,\n} from './components'\nexport {\n type ColorScheme,\n getEnvironment,\n type KeyboardState,\n type PlatformEnvironment,\n type SafeAreaInsets,\n setEnvironment,\n useColorScheme,\n useKeyboard,\n useSafeAreaInsets,\n useWindowDimensions,\n type WindowDimensions,\n} from './environment'\nexport { type BaseProps, type Reactive, resolveStyle, toHostProps } from './host'\nexport {\n FocusScope,\n type FocusScopeProps,\n Modal,\n type ModalProps,\n} from './overlay'\nexport {\n Button,\n type ButtonProps,\n Column,\n Image,\n type ImageProps,\n type InteractionState,\n Pressable,\n type PressableProps,\n Row,\n ScrollView,\n type ScrollViewProps,\n Spacer,\n type SpacerProps,\n Stack,\n type StackProps,\n Text,\n TextInput,\n type TextInputProps,\n type TextProps,\n usePressable,\n View,\n type ViewProps,\n} from './primitives'\nexport { flattenStyle, type StyleInput, type StyleObject, type StyleValue } from './style'\nexport {\n duration,\n easing,\n fontSize,\n fontWeight,\n getTheme,\n lineHeight,\n palette,\n radius,\n space,\n type Theme,\n type ThemeColors,\n tokens,\n useTheme,\n} from './tokens'\nexport type { Maturity, PackageInfo }\nexport { NotImplementedError, notImplemented }\n"],"mappings":";;;;;;;;;;;AAeA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;AAGvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","names":[],"sources":["../src/list.ts"],"mappings":";;;;;;UA6BiB,UAAA;EAef;EAAA,SAbS,UAAA;EAeT;EAAA,SAbS,QAAA;EAeR;EAAA,SAbQ,WAAA;AAAA;AAyBX;;;;AAAA,iBAlBgB,aAAA,CACd,SAAA,UACA,cAAA,UACA,UAAA,UACA,SAAA,UACA,QAAA,WACC,UAAU;;UAYI,WAAA;EAwBW;EAAA,SAtBjB,KAAA,WAAgB,CAAA,qBAAsB,CAAA;EAsBtB;;;;;;EAAA,SAfhB,UAAA,GAAa,IAAA,QAAY,CAAA,EAAG,KAAA,mBAAwB,WAAA;EAA3B;EAAA,SAEzB,UAAA;EAF4B;EAAA,SAI5B,MAAA;EAFA;EAAA,SAIA,QAAA;EAAA;EAAA,SAEA,eAAA;EAKA;;;;EAAA,SAAA,YAAA;EAE2B;EAAA,SAA3B,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;;;;;;iBAoBZ,UAAA,IAAc,OAAA,EAAS,WAAA,CAAY,CAAA,IAAK,WAAA;;iBA2HxC,IAAA,IAAQ,OAAA,EAAS,WAAA,CAAY,CAAA,IAAK,WAAA;;UASjC,OAAA;EApIuC;EAAA,SAsI7C,KAAA;EAAA,SACA,IAAA,WAAe,CAAC;EAZX;EAAA,SAcL,GAAA;AAAA;;KAIN,KAAA;EAAA,SACU,IAAA;EAAA,SAAyB,OAAA,EAAS,OAAA,CAAQ,CAAA;EAAA,SAAa,YAAA;AAAA;EAAA,SAEvD,IAAA;EAAA,SACA,OAAA,EAAS,OAAA,CAAQ,CAAA;EAAA,SACjB,IAAA,EAAM,CAAA;EAAA,SACN,YAAA;EAAA,SACA,SAAA;AAAA;AAhBf;AAAA,iBAoBgB,eAAA,IAAmB,QAAA,WAAmB,OAAA,CAAQ,CAAA,MAAO,KAAA,CAAM,CAAA;;UAY1D,kBAAA;EAhCQ;EAAA,SAkCd,QAAA,WAAmB,OAAA,CAAQ,CAAA,sBAAuB,OAAA,CAAQ,CAAA;EA/B1D;EAAA,SAiCA,UAAA,GAAa,IAAA,QAAY,CAAA,EAAG,KAAA,mBAAwB,WAAA;EA/BpD;EAAA,SAiCA,mBAAA,IAAuB,OAAA,QAAe,OAAA,CAAQ,CAAA,MAAO,WAAA;EAjClD;EAAA,SAmCH,UAAA;EAAA,SACA,MAAA;EAAA,SACA,QAAA;EAAA,SACA,YAAA;EAAA,SACA,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;;;;;;iBAQZ,iBAAA,IAAqB,OAAA,EAAS,kBAAA,CAAmB,CAAA,IAAK,WAAA;;iBA+BtD,WAAA,IAAe,OAAA,EAAS,kBAAA,CAAmB,CAAA,IAAK,WAAA"}
1
+ {"version":3,"file":"list.d.ts","names":[],"sources":["../src/list.ts"],"mappings":";;;;;;UA6BiB,UAAA;EAef;EAAA,SAbS,UAAA;EAeT;EAAA,SAbS,QAAA;EAeR;EAAA,SAbQ,WAAA;AAAA;AAyBX;;;;AAAA,iBAlBgB,aAAA,CACd,SAAA,UACA,cAAA,UACA,UAAA,UACA,SAAA,UACA,QAAA,WACC,UAAU;;UAYI,WAAA;EAwBW;EAAA,SAtBjB,KAAA,WAAgB,CAAA,qBAAsB,CAAA;EAsBtB;;;;;;EAAA,SAfhB,UAAA,GAAa,IAAA,QAAY,CAAA,EAAG,KAAA,mBAAwB,WAAA;EAA3B;EAAA,SAEzB,UAAA;EAF4B;EAAA,SAI5B,MAAA;EAFA;EAAA,SAIA,QAAA;EAAA;EAAA,SAEA,eAAA;EAKA;;;;EAAA,SAAA,YAAA;EAE2B;EAAA,SAA3B,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;;;;;;iBAoBZ,UAAA,IAAc,OAAA,EAAS,WAAA,CAAY,CAAA,IAAK,WAAA;;iBAoIxC,IAAA,IAAQ,OAAA,EAAS,WAAA,CAAY,CAAA,IAAK,WAAA;;UASjC,OAAA;EA7IuC;EAAA,SA+I7C,KAAA;EAAA,SACA,IAAA,WAAe,CAAC;EAZX;EAAA,SAcL,GAAA;AAAA;;KAIN,KAAA;EAAA,SACU,IAAA;EAAA,SAAyB,OAAA,EAAS,OAAA,CAAQ,CAAA;EAAA,SAAa,YAAA;AAAA;EAAA,SAEvD,IAAA;EAAA,SACA,OAAA,EAAS,OAAA,CAAQ,CAAA;EAAA,SACjB,IAAA,EAAM,CAAA;EAAA,SACN,YAAA;EAAA,SACA,SAAA;AAAA;AAhBf;AAAA,iBAoBgB,eAAA,IAAmB,QAAA,WAAmB,OAAA,CAAQ,CAAA,MAAO,KAAA,CAAM,CAAA;;UAY1D,kBAAA;EAhCQ;EAAA,SAkCd,QAAA,WAAmB,OAAA,CAAQ,CAAA,sBAAuB,OAAA,CAAQ,CAAA;EA/B1D;EAAA,SAiCA,UAAA,GAAa,IAAA,QAAY,CAAA,EAAG,KAAA,mBAAwB,WAAA;EA/BpD;EAAA,SAiCA,mBAAA,IAAuB,OAAA,QAAe,OAAA,CAAQ,CAAA,MAAO,WAAA;EAjClD;EAAA,SAmCH,UAAA;EAAA,SACA,MAAA;EAAA,SACA,QAAA;EAAA,SACA,YAAA;EAAA,SACA,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;;;;;;iBAQZ,iBAAA,IAAqB,OAAA,EAAS,kBAAA,CAAmB,CAAA,IAAK,WAAA;;iBA+BtD,WAAA,IAAe,OAAA,EAAS,kBAAA,CAAmB,CAAA,IAAK,WAAA"}
package/dist/list.js CHANGED
@@ -105,12 +105,14 @@ function createList(options) {
105
105
  })
106
106
  }, content);
107
107
  }));
108
+ const baseScrollStyle = {
109
+ height,
110
+ position: "relative"
111
+ };
112
+ const callerStyle = options.style;
108
113
  return createElement(ScrollView, {
109
114
  onScroll: (event) => scrollTop.set(readScrollTop(event)),
110
- style: flattenStyle([{
111
- height,
112
- position: "relative"
113
- }, options.style])
115
+ style: typeof callerStyle === "function" ? () => flattenStyle([baseScrollStyle, callerStyle()]) : flattenStyle([baseScrollStyle, callerStyle])
114
116
  }, spacer);
115
117
  };
116
118
  }
package/dist/list.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","names":[],"sources":["../src/list.ts"],"sourcesContent":["/**\n * Atlas `List` — a virtualized, **recycling** list. Only the visible window (+ overscan) is\n * rendered; rows are a FIXED POOL of per-slot reactive regions, NOT one region returning\n * `items.map(...)` (the Helix reconciler has no keyed array diff, so a single array region\n * would tear down + remount every visible row on each scroll). Each slot region depends only\n * on its own `active` signal, so a row that stays in view keeps its identity and `renderItem`\n * runs once for it — only rows scrolling across the window edge are (re)created. A total-height\n * spacer keeps the native scrollbar correct; rows are absolutely positioned by `transform`.\n *\n * Fixed row height in v1 (deterministic windowing, headless-testable with zero real scroll);\n * variable/measured heights are a 🔬 research track. See `docs/adr/0023-atlas-list.md`.\n *\n * @module\n */\n\nimport {\n batch,\n createElement,\n effect,\n type MindeesNode,\n memo,\n type Signal,\n signal,\n} from '@mindees/core'\nimport type { Reactive } from './host'\nimport { ScrollView } from './primitives'\nimport { flattenStyle, type StyleInput } from './style'\n\n/** The computed visible window over the item list. */\nexport interface ListWindow {\n /** First visible index (inclusive), overscan applied. */\n readonly startIndex: number\n /** Last visible index (exclusive), overscan applied. */\n readonly endIndex: number\n /** Total scrollable height (px) = itemCount × itemHeight. */\n readonly totalHeight: number\n}\n\n/**\n * Pure window math: which item indices are visible for a given scroll offset. Exported and\n * exhaustively unit-tested — the deterministic heart of virtualization (no signals, no DOM).\n */\nexport function computeWindow(\n scrollTop: number,\n viewportHeight: number,\n itemHeight: number,\n itemCount: number,\n overscan: number,\n): ListWindow {\n const totalHeight = itemCount * itemHeight\n if (itemCount <= 0) return { startIndex: 0, endIndex: 0, totalHeight: 0 }\n const top = Math.max(0, Math.min(scrollTop, Math.max(0, totalHeight - viewportHeight)))\n const firstVisible = Math.floor(top / itemHeight)\n const lastVisible = Math.ceil((top + viewportHeight) / itemHeight)\n const startIndex = Math.max(0, firstVisible - overscan)\n const endIndex = Math.min(itemCount, lastVisible + overscan)\n return { startIndex, endIndex, totalHeight }\n}\n\n/** Options for {@link createList}. */\nexport interface ListOptions<T> {\n /** The items, static or reactive. */\n readonly items: readonly T[] | (() => readonly T[])\n /**\n * Render one row. Receives reactive `item`/`index` **accessors** — consume them LAZILY\n * (`Text({ children: () => item().name })`, a `style` fn, or pass them to a child) so a\n * recycled slot patches in place. Reading `item()`/`index()` synchronously in the body bakes\n * the value in and opts the row out of recycling (it re-runs renderItem on reuse instead).\n */\n readonly renderItem: (item: () => T, index: () => number) => MindeesNode\n /** Fixed row height in px (variable heights are a research track). */\n readonly itemHeight: number\n /** Viewport height in px. */\n readonly height: number\n /** Extra rows rendered above/below the viewport (default 3, clamped 0–50). */\n readonly overscan?: number\n /** Read the current scroll offset (test/SSR injection seam; default 0). */\n readonly getScrollOffset?: () => number\n /**\n * Fires once when the last item is within the rendered window — including at mount if the list\n * already fits the viewport — and re-arms when the end scrolls back out (e.g. to load more).\n */\n readonly onEndReached?: () => void\n /** Extra style on the scroll container. */\n readonly style?: Reactive<StyleInput>\n}\n\ninterface Slot<T> {\n readonly item: Signal<T | undefined>\n readonly index: Signal<number>\n readonly top: Signal<number>\n readonly active: Signal<boolean>\n}\n\nfunction readScrollTop(event: unknown): number {\n const top = (event as { target?: { scrollTop?: number } })?.target?.scrollTop\n return typeof top === 'number' && Number.isFinite(top) ? top : 0\n}\n\n/**\n * Create a virtualized recycling list as a renderer-agnostic `MindeesNode`.\n *\n * @throws RangeError if `itemHeight`/`height` are not positive finite numbers.\n */\nexport function createList<T>(options: ListOptions<T>): MindeesNode {\n const { renderItem, itemHeight, height } = options\n if (!Number.isFinite(itemHeight) || itemHeight <= 0) {\n throw new RangeError('List itemHeight must be a positive number')\n }\n if (!Number.isFinite(height) || height <= 0) {\n throw new RangeError('List height must be a positive number')\n }\n const overscan = Math.max(0, Math.min(50, Math.floor(options.overscan ?? 3)))\n const itemsOf: () => readonly T[] =\n typeof options.items === 'function' ? options.items : () => options.items as readonly T[]\n\n // Return a reactive-region accessor so ALL the signals/memo/effect below are created under the\n // mounting owner (the renderer runs this once via bindReactiveChild) and are disposed on\n // unmount. Creating them eagerly here would leave them un-owned (currentOwner === null at call\n // time) and leak past `dispose()`. Validation above stays synchronous (throws at call time).\n return () => {\n const scrollTop = signal(options.getScrollOffset ? options.getScrollOffset() : 0)\n const poolSize = Math.ceil(height / itemHeight) + 2 * overscan + 1\n\n // Re-run the assignment only when the integer window actually changes (not every pixel).\n const windowMemo = memo(\n () => computeWindow(scrollTop(), height, itemHeight, itemsOf().length, overscan),\n {\n equals: (a, b) => a.startIndex === b.startIndex && a.endIndex === b.endIndex,\n },\n )\n\n const slots: Slot<T>[] = Array.from({ length: poolSize }, () => ({\n item: signal<T | undefined>(undefined),\n index: signal(0),\n top: signal(0),\n active: signal(false),\n }))\n\n let endReachedFired = false\n // Assign each visible index to slot `index % poolSize`; deactivate the rest. Max window size\n // equals poolSize (the `+1` is that exact margin — do NOT remove it), and any run of ≤\n // poolSize consecutive indices has distinct residues mod poolSize, so no two visible indices\n // ever share a slot.\n effect(() => {\n const { startIndex, endIndex } = windowMemo()\n const items = itemsOf()\n const used = new Array<boolean>(poolSize).fill(false)\n batch(() => {\n for (let i = startIndex; i < endIndex; i++) {\n const s = ((i % poolSize) + poolSize) % poolSize\n used[s] = true\n const slot = slots[s]\n if (!slot) continue\n slot.item.set(items[i])\n slot.index.set(i)\n slot.top.set(i * itemHeight)\n slot.active.set(true)\n }\n for (let s = 0; s < poolSize; s++) {\n if (!used[s]) slots[s]?.active.set(false)\n }\n })\n // onEndReached: fire when the last item is within the window (incl. at mount if the list\n // fits the viewport); re-arm when the end leaves the window.\n if (options.onEndReached && items.length > 0) {\n if (endIndex >= items.length) {\n if (!endReachedFired) {\n endReachedFired = true\n options.onEndReached()\n }\n } else {\n endReachedFired = false\n }\n }\n })\n\n // One reactive region per slot. The body depends ONLY on `active()`, so a slot that stays\n // active never re-runs renderItem; item/index/top flow through the inner accessors — provided\n // renderItem consumes them lazily (see the ListOptions.renderItem contract).\n const rows: MindeesNode[] = slots.map((slot, s) => () => {\n if (!slot.active()) return null\n const content = renderItem(\n () => slot.item() as T,\n () => slot.index(),\n )\n return createElement(\n 'view',\n {\n key: `atlas-list-row-${s}`,\n style: () => ({\n position: 'absolute',\n left: 0,\n right: 0,\n top: 0,\n height: itemHeight,\n transform: `translateY(${slot.top()}px)`,\n }),\n },\n content,\n )\n })\n\n const spacer = createElement(\n 'view',\n {\n style: () => ({\n position: 'relative',\n width: '100%',\n height: itemsOf().length * itemHeight,\n }),\n },\n ...rows,\n )\n\n return createElement(\n ScrollView,\n {\n onScroll: (event: unknown) => scrollTop.set(readScrollTop(event)),\n style: flattenStyle([{ height, position: 'relative' }, options.style as StyleInput]),\n },\n spacer,\n )\n }\n}\n\n/** Component-style alias for {@link createList}. */\nexport function List<T>(options: ListOptions<T>): MindeesNode {\n return createList(options)\n}\n\n// ---------------------------------------------------------------------------\n// SectionList\n// ---------------------------------------------------------------------------\n\n/** A list section: an optional header + its rows. */\nexport interface Section<T> {\n /** Header title (used by the default header when `renderSectionHeader` is omitted). */\n readonly title?: string\n readonly data: readonly T[]\n /** Stable key for the section (optional). */\n readonly key?: string\n}\n\n/** A flattened section-list entry: a header or a row. */\ntype Entry<T> =\n | { readonly kind: 'header'; readonly section: Section<T>; readonly sectionIndex: number }\n | {\n readonly kind: 'item'\n readonly section: Section<T>\n readonly item: T\n readonly sectionIndex: number\n readonly itemIndex: number\n }\n\n/** Flatten sections into a single ordered entry list (header, its rows, next header, …). */\nexport function flattenSections<T>(sections: readonly Section<T>[]): Entry<T>[] {\n const out: Entry<T>[] = []\n sections.forEach((section, sectionIndex) => {\n out.push({ kind: 'header', section, sectionIndex })\n section.data.forEach((item, itemIndex) => {\n out.push({ kind: 'item', section, item, sectionIndex, itemIndex })\n })\n })\n return out\n}\n\n/** Options for {@link createSectionList}. */\nexport interface SectionListOptions<T> {\n /** The sections, static or reactive. */\n readonly sections: readonly Section<T>[] | (() => readonly Section<T>[])\n /** Render one row (same lazy-accessor contract as {@link ListOptions.renderItem}). */\n readonly renderItem: (item: () => T, index: () => number) => MindeesNode\n /** Render a section header (defaults to a `text` of `section.title`). */\n readonly renderSectionHeader?: (section: () => Section<T>) => MindeesNode\n /** Fixed row height in px (headers and rows share it in v1). */\n readonly itemHeight: number\n readonly height: number\n readonly overscan?: number\n readonly onEndReached?: () => void\n readonly style?: Reactive<StyleInput>\n}\n\n/**\n * A virtualized **section list** built on {@link createList}: sections are flattened to a\n * single entry stream (header, rows, …) and windowed, so only visible headers/rows render.\n * Fixed row height in v1 (headers share it) — variable heights track the List's research item.\n */\nexport function createSectionList<T>(options: SectionListOptions<T>): MindeesNode {\n const sectionsOf: () => readonly Section<T>[] =\n typeof options.sections === 'function'\n ? options.sections\n : () => options.sections as readonly Section<T>[]\n\n return createList<Entry<T>>({\n items: () => flattenSections(sectionsOf()),\n itemHeight: options.itemHeight,\n height: options.height,\n ...(options.overscan !== undefined ? { overscan: options.overscan } : {}),\n ...(options.onEndReached ? { onEndReached: options.onEndReached } : {}),\n ...(options.style !== undefined ? { style: options.style } : {}),\n // The branch reads `entry()`, so a recycled slot re-runs this when its entry changes —\n // correct for mixed header/row content (rows still virtualize: only the visible window renders).\n renderItem: (entry) => () => {\n const e = entry()\n if (e.kind === 'header') {\n return options.renderSectionHeader\n ? options.renderSectionHeader(() => entry().section)\n : createElement('text', null, e.section.title ?? '')\n }\n return options.renderItem(\n () => (entry() as Extract<Entry<T>, { kind: 'item' }>).item,\n () => (entry() as Extract<Entry<T>, { kind: 'item' }>).itemIndex,\n )\n },\n })\n}\n\n/** Component-style alias for {@link createSectionList}. */\nexport function SectionList<T>(options: SectionListOptions<T>): MindeesNode {\n return createSectionList(options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"list.js","names":[],"sources":["../src/list.ts"],"sourcesContent":["/**\n * Atlas `List` — a virtualized, **recycling** list. Only the visible window (+ overscan) is\n * rendered; rows are a FIXED POOL of per-slot reactive regions, NOT one region returning\n * `items.map(...)` (the Helix reconciler has no keyed array diff, so a single array region\n * would tear down + remount every visible row on each scroll). Each slot region depends only\n * on its own `active` signal, so a row that stays in view keeps its identity and `renderItem`\n * runs once for it — only rows scrolling across the window edge are (re)created. A total-height\n * spacer keeps the native scrollbar correct; rows are absolutely positioned by `transform`.\n *\n * Fixed row height in v1 (deterministic windowing, headless-testable with zero real scroll);\n * variable/measured heights are a 🔬 research track. See `docs/adr/0023-atlas-list.md`.\n *\n * @module\n */\n\nimport {\n batch,\n createElement,\n effect,\n type MindeesNode,\n memo,\n type Signal,\n signal,\n} from '@mindees/core'\nimport type { Reactive } from './host'\nimport { ScrollView } from './primitives'\nimport { flattenStyle, type StyleInput } from './style'\n\n/** The computed visible window over the item list. */\nexport interface ListWindow {\n /** First visible index (inclusive), overscan applied. */\n readonly startIndex: number\n /** Last visible index (exclusive), overscan applied. */\n readonly endIndex: number\n /** Total scrollable height (px) = itemCount × itemHeight. */\n readonly totalHeight: number\n}\n\n/**\n * Pure window math: which item indices are visible for a given scroll offset. Exported and\n * exhaustively unit-tested — the deterministic heart of virtualization (no signals, no DOM).\n */\nexport function computeWindow(\n scrollTop: number,\n viewportHeight: number,\n itemHeight: number,\n itemCount: number,\n overscan: number,\n): ListWindow {\n const totalHeight = itemCount * itemHeight\n if (itemCount <= 0) return { startIndex: 0, endIndex: 0, totalHeight: 0 }\n const top = Math.max(0, Math.min(scrollTop, Math.max(0, totalHeight - viewportHeight)))\n const firstVisible = Math.floor(top / itemHeight)\n const lastVisible = Math.ceil((top + viewportHeight) / itemHeight)\n const startIndex = Math.max(0, firstVisible - overscan)\n const endIndex = Math.min(itemCount, lastVisible + overscan)\n return { startIndex, endIndex, totalHeight }\n}\n\n/** Options for {@link createList}. */\nexport interface ListOptions<T> {\n /** The items, static or reactive. */\n readonly items: readonly T[] | (() => readonly T[])\n /**\n * Render one row. Receives reactive `item`/`index` **accessors** — consume them LAZILY\n * (`Text({ children: () => item().name })`, a `style` fn, or pass them to a child) so a\n * recycled slot patches in place. Reading `item()`/`index()` synchronously in the body bakes\n * the value in and opts the row out of recycling (it re-runs renderItem on reuse instead).\n */\n readonly renderItem: (item: () => T, index: () => number) => MindeesNode\n /** Fixed row height in px (variable heights are a research track). */\n readonly itemHeight: number\n /** Viewport height in px. */\n readonly height: number\n /** Extra rows rendered above/below the viewport (default 3, clamped 0–50). */\n readonly overscan?: number\n /** Read the current scroll offset (test/SSR injection seam; default 0). */\n readonly getScrollOffset?: () => number\n /**\n * Fires once when the last item is within the rendered window — including at mount if the list\n * already fits the viewport — and re-arms when the end scrolls back out (e.g. to load more).\n */\n readonly onEndReached?: () => void\n /** Extra style on the scroll container. */\n readonly style?: Reactive<StyleInput>\n}\n\ninterface Slot<T> {\n readonly item: Signal<T | undefined>\n readonly index: Signal<number>\n readonly top: Signal<number>\n readonly active: Signal<boolean>\n}\n\nfunction readScrollTop(event: unknown): number {\n const top = (event as { target?: { scrollTop?: number } })?.target?.scrollTop\n return typeof top === 'number' && Number.isFinite(top) ? top : 0\n}\n\n/**\n * Create a virtualized recycling list as a renderer-agnostic `MindeesNode`.\n *\n * @throws RangeError if `itemHeight`/`height` are not positive finite numbers.\n */\nexport function createList<T>(options: ListOptions<T>): MindeesNode {\n const { renderItem, itemHeight, height } = options\n if (!Number.isFinite(itemHeight) || itemHeight <= 0) {\n throw new RangeError('List itemHeight must be a positive number')\n }\n if (!Number.isFinite(height) || height <= 0) {\n throw new RangeError('List height must be a positive number')\n }\n const overscan = Math.max(0, Math.min(50, Math.floor(options.overscan ?? 3)))\n const itemsOf: () => readonly T[] =\n typeof options.items === 'function' ? options.items : () => options.items as readonly T[]\n\n // Return a reactive-region accessor so ALL the signals/memo/effect below are created under the\n // mounting owner (the renderer runs this once via bindReactiveChild) and are disposed on\n // unmount. Creating them eagerly here would leave them un-owned (currentOwner === null at call\n // time) and leak past `dispose()`. Validation above stays synchronous (throws at call time).\n return () => {\n const scrollTop = signal(options.getScrollOffset ? options.getScrollOffset() : 0)\n const poolSize = Math.ceil(height / itemHeight) + 2 * overscan + 1\n\n // Re-run the assignment only when the integer window actually changes (not every pixel).\n const windowMemo = memo(\n () => computeWindow(scrollTop(), height, itemHeight, itemsOf().length, overscan),\n {\n equals: (a, b) => a.startIndex === b.startIndex && a.endIndex === b.endIndex,\n },\n )\n\n const slots: Slot<T>[] = Array.from({ length: poolSize }, () => ({\n item: signal<T | undefined>(undefined),\n index: signal(0),\n top: signal(0),\n active: signal(false),\n }))\n\n let endReachedFired = false\n // Assign each visible index to slot `index % poolSize`; deactivate the rest. Max window size\n // equals poolSize (the `+1` is that exact margin — do NOT remove it), and any run of ≤\n // poolSize consecutive indices has distinct residues mod poolSize, so no two visible indices\n // ever share a slot.\n effect(() => {\n const { startIndex, endIndex } = windowMemo()\n const items = itemsOf()\n const used = new Array<boolean>(poolSize).fill(false)\n batch(() => {\n for (let i = startIndex; i < endIndex; i++) {\n const s = ((i % poolSize) + poolSize) % poolSize\n used[s] = true\n const slot = slots[s]\n if (!slot) continue\n slot.item.set(items[i])\n slot.index.set(i)\n slot.top.set(i * itemHeight)\n slot.active.set(true)\n }\n for (let s = 0; s < poolSize; s++) {\n if (!used[s]) slots[s]?.active.set(false)\n }\n })\n // onEndReached: fire when the last item is within the window (incl. at mount if the list\n // fits the viewport); re-arm when the end leaves the window.\n if (options.onEndReached && items.length > 0) {\n if (endIndex >= items.length) {\n if (!endReachedFired) {\n endReachedFired = true\n options.onEndReached()\n }\n } else {\n endReachedFired = false\n }\n }\n })\n\n // One reactive region per slot. The body depends ONLY on `active()`, so a slot that stays\n // active never re-runs renderItem; item/index/top flow through the inner accessors — provided\n // renderItem consumes them lazily (see the ListOptions.renderItem contract).\n const rows: MindeesNode[] = slots.map((slot, s) => () => {\n if (!slot.active()) return null\n const content = renderItem(\n () => slot.item() as T,\n () => slot.index(),\n )\n return createElement(\n 'view',\n {\n key: `atlas-list-row-${s}`,\n style: () => ({\n position: 'absolute',\n left: 0,\n right: 0,\n top: 0,\n height: itemHeight,\n transform: `translateY(${slot.top()}px)`,\n }),\n },\n content,\n )\n })\n\n const spacer = createElement(\n 'view',\n {\n style: () => ({\n position: 'relative',\n width: '100%',\n height: itemsOf().length * itemHeight,\n }),\n },\n ...rows,\n )\n\n // A reactive (accessor) `style` must stay reactive — eagerly flattening it would\n // Object.assign a function and silently drop it. Keep it a function when the caller's is.\n const baseScrollStyle: StyleInput = { height, position: 'relative' }\n const callerStyle = options.style\n const scrollStyle: Reactive<StyleInput> =\n typeof callerStyle === 'function'\n ? () => flattenStyle([baseScrollStyle, (callerStyle as () => StyleInput)()])\n : flattenStyle([baseScrollStyle, callerStyle as StyleInput])\n\n return createElement(\n ScrollView,\n {\n onScroll: (event: unknown) => scrollTop.set(readScrollTop(event)),\n style: scrollStyle,\n },\n spacer,\n )\n }\n}\n\n/** Component-style alias for {@link createList}. */\nexport function List<T>(options: ListOptions<T>): MindeesNode {\n return createList(options)\n}\n\n// ---------------------------------------------------------------------------\n// SectionList\n// ---------------------------------------------------------------------------\n\n/** A list section: an optional header + its rows. */\nexport interface Section<T> {\n /** Header title (used by the default header when `renderSectionHeader` is omitted). */\n readonly title?: string\n readonly data: readonly T[]\n /** Stable key for the section (optional). */\n readonly key?: string\n}\n\n/** A flattened section-list entry: a header or a row. */\ntype Entry<T> =\n | { readonly kind: 'header'; readonly section: Section<T>; readonly sectionIndex: number }\n | {\n readonly kind: 'item'\n readonly section: Section<T>\n readonly item: T\n readonly sectionIndex: number\n readonly itemIndex: number\n }\n\n/** Flatten sections into a single ordered entry list (header, its rows, next header, …). */\nexport function flattenSections<T>(sections: readonly Section<T>[]): Entry<T>[] {\n const out: Entry<T>[] = []\n sections.forEach((section, sectionIndex) => {\n out.push({ kind: 'header', section, sectionIndex })\n section.data.forEach((item, itemIndex) => {\n out.push({ kind: 'item', section, item, sectionIndex, itemIndex })\n })\n })\n return out\n}\n\n/** Options for {@link createSectionList}. */\nexport interface SectionListOptions<T> {\n /** The sections, static or reactive. */\n readonly sections: readonly Section<T>[] | (() => readonly Section<T>[])\n /** Render one row (same lazy-accessor contract as {@link ListOptions.renderItem}). */\n readonly renderItem: (item: () => T, index: () => number) => MindeesNode\n /** Render a section header (defaults to a `text` of `section.title`). */\n readonly renderSectionHeader?: (section: () => Section<T>) => MindeesNode\n /** Fixed row height in px (headers and rows share it in v1). */\n readonly itemHeight: number\n readonly height: number\n readonly overscan?: number\n readonly onEndReached?: () => void\n readonly style?: Reactive<StyleInput>\n}\n\n/**\n * A virtualized **section list** built on {@link createList}: sections are flattened to a\n * single entry stream (header, rows, …) and windowed, so only visible headers/rows render.\n * Fixed row height in v1 (headers share it) — variable heights track the List's research item.\n */\nexport function createSectionList<T>(options: SectionListOptions<T>): MindeesNode {\n const sectionsOf: () => readonly Section<T>[] =\n typeof options.sections === 'function'\n ? options.sections\n : () => options.sections as readonly Section<T>[]\n\n return createList<Entry<T>>({\n items: () => flattenSections(sectionsOf()),\n itemHeight: options.itemHeight,\n height: options.height,\n ...(options.overscan !== undefined ? { overscan: options.overscan } : {}),\n ...(options.onEndReached ? { onEndReached: options.onEndReached } : {}),\n ...(options.style !== undefined ? { style: options.style } : {}),\n // The branch reads `entry()`, so a recycled slot re-runs this when its entry changes —\n // correct for mixed header/row content (rows still virtualize: only the visible window renders).\n renderItem: (entry) => () => {\n const e = entry()\n if (e.kind === 'header') {\n return options.renderSectionHeader\n ? options.renderSectionHeader(() => entry().section)\n : createElement('text', null, e.section.title ?? '')\n }\n return options.renderItem(\n () => (entry() as Extract<Entry<T>, { kind: 'item' }>).item,\n () => (entry() as Extract<Entry<T>, { kind: 'item' }>).itemIndex,\n )\n },\n })\n}\n\n/** Component-style alias for {@link createSectionList}. */\nexport function SectionList<T>(options: SectionListOptions<T>): MindeesNode {\n return createSectionList(options)\n}\n"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { A11yProps, Role } from "./a11y.js";
2
+ import { StyleInput } from "./style.js";
3
+ import { Reactive } from "./host.js";
4
+ import { Component, MindeesNode } from "@mindees/core";
5
+
6
+ //#region src/overlay.d.ts
7
+ /** Props for {@link FocusScope}. */
8
+ interface FocusScopeProps {
9
+ readonly children?: MindeesNode;
10
+ /** Focus the scope container on mount (default true). */
11
+ readonly autoFocus?: boolean;
12
+ /** Restore focus to the previously-focused element on unmount (default true). */
13
+ readonly restoreFocus?: boolean;
14
+ /** Called when Escape is pressed inside the scope. */
15
+ readonly onEscape?: () => void;
16
+ /** Dialog role (default `'dialog'`). */
17
+ readonly role?: Role;
18
+ /** Accessible name (`aria-label`). */
19
+ readonly label?: string;
20
+ /** Extra style on the scope container. */
21
+ readonly style?: Reactive<StyleInput>;
22
+ }
23
+ /** A focus-scoped, `aria-modal` container. Captures + restores focus on web; declarative elsewhere. */
24
+ declare const FocusScope: Component<FocusScopeProps>;
25
+ /** Props for {@link Modal}. */
26
+ interface ModalProps extends A11yProps {
27
+ /** Whether the modal is open (static or reactive). */
28
+ readonly visible: Reactive<boolean>;
29
+ /** Requested close (scrim press or Escape). */
30
+ readonly onRequestClose?: () => void;
31
+ readonly children?: MindeesNode;
32
+ /** Close when the scrim is pressed (default true). */
33
+ readonly closeOnBackdrop?: boolean;
34
+ /** Extra style merged into the scrim. */
35
+ readonly backdrop?: Reactive<StyleInput>;
36
+ /** Explicit overlay host target (else the backend's overlay layer). */
37
+ readonly mount?: unknown;
38
+ }
39
+ /** A portal-backed modal dialog: a dismissable scrim + a focus-scoped dialog above the tree. */
40
+ declare const Modal: Component<ModalProps>;
41
+ //#endregion
42
+ export { FocusScope, FocusScopeProps, Modal, ModalProps };
43
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","names":[],"sources":["../src/overlay.ts"],"mappings":";;;;;;;UA8BiB,eAAA;EAAA,SACN,QAAA,GAAW,WAAA;EAYX;EAAA,SAVA,SAAA;EAUiB;EAAA,SARjB,YAAA;EAQ2B;EAAA,SAN3B,QAAA;EAoDV;EAAA,SAlDU,IAAA,GAAO,IAAA;EAQO;EAAA,SANd,KAAA;EAmDM;EAAA,SAjDN,KAAA,GAAQ,QAAA,CAAS,UAAA;AAAA;;cAIf,UAAA,EAAY,SAAS,CAAC,eAAA;;UA6ClB,UAAA,SAAmB,SAAA;EAAA;EAAA,SAEzB,OAAA,EAAS,QAAA;EAFyB;EAAA,SAIlC,cAAA;EAAA,SACA,QAAA,GAAW,WAAA;EAHF;EAAA,SAKT,eAAA;EAFA;EAAA,SAIA,QAAA,GAAW,QAAA,CAAS,UAAA;EAFpB;EAAA,SAIA,KAAA;AAAA;;cAIE,KAAA,EAAO,SAAS,CAAC,UAAA"}
@@ -0,0 +1,88 @@
1
+ import { flattenStyle } from "./style.js";
2
+ import { Pressable, View } from "./primitives.js";
3
+ import { createElement, onCleanup, portal } from "@mindees/core";
4
+ //#region src/overlay.ts
5
+ /**
6
+ * Atlas overlays — `Modal` + `FocusScope`, built on core's `portal`.
7
+ *
8
+ * `Modal` gates a portal by a reactive `visible`: on open it relocates a scrim + a focus-scoped
9
+ * dialog to the renderer's overlay layer (above the tree); on close the gating region re-runs,
10
+ * firing the portal's cleanup (unmount) and the FocusScope's focus-restore. `FocusScope` captures
11
+ * the previously-focused element, auto-focuses its container, and restores focus on unmount —
12
+ * DOM-feature-detected, so it no-ops on native/headless (the dialog markup + a11y still serialize).
13
+ *
14
+ * v1 scope: web is fully interactive (scrim dismiss, Escape, focus capture/restore). Native is
15
+ * declarative — `role="dialog"` + `aria-modal` are carried to the host; a true focus trap +
16
+ * tab-cycling and back-button handling are a host follow-up (see the portal-modal ADR). Tab
17
+ * cycling within the scope is also deferred (needs a descendant query).
18
+ *
19
+ * @module
20
+ */
21
+ function toAccessor(value, fallback) {
22
+ return typeof value === "function" ? value : () => value === void 0 ? fallback : value;
23
+ }
24
+ /** A focus-scoped, `aria-modal` container. Captures + restores focus on web; declarative elsewhere. */
25
+ const FocusScope = (props) => {
26
+ let restore = null;
27
+ const captureAndFocus = (host) => {
28
+ if (typeof document === "undefined") return;
29
+ const node = host;
30
+ const previous = document.activeElement ?? null;
31
+ if (props.restoreFocus !== false && previous && typeof previous.focus === "function") restore = () => previous.focus?.();
32
+ if (props.autoFocus !== false && node && typeof node.focus === "function") queueMicrotask(() => {
33
+ if (node.isConnected) node.focus?.();
34
+ });
35
+ };
36
+ onCleanup(() => restore?.());
37
+ const callerStyle = props.style;
38
+ const style = () => flattenStyle([{ position: "relative" }, typeof callerStyle === "function" ? callerStyle() : callerStyle ?? {}]);
39
+ const hostProps = {
40
+ role: props.role ?? "dialog",
41
+ "aria-modal": "true",
42
+ tabindex: -1,
43
+ ref: captureAndFocus,
44
+ style
45
+ };
46
+ if (props.label !== void 0) hostProps["aria-label"] = props.label;
47
+ if (props.onEscape !== void 0) hostProps.onKeyDown = (e) => {
48
+ if (e.key === "Escape") props.onEscape?.();
49
+ };
50
+ return createElement("view", hostProps, props.children);
51
+ };
52
+ /** A portal-backed modal dialog: a dismissable scrim + a focus-scoped dialog above the tree. */
53
+ const Modal = (props) => {
54
+ const isVisible = toAccessor(props.visible, false);
55
+ return () => {
56
+ if (!isVisible()) return null;
57
+ const scrimStyle = () => flattenStyle([{
58
+ position: "absolute",
59
+ top: 0,
60
+ right: 0,
61
+ bottom: 0,
62
+ left: 0,
63
+ backgroundColor: "rgba(0,0,0,0.5)"
64
+ }, typeof props.backdrop === "function" ? props.backdrop() : props.backdrop ?? {}]);
65
+ return portal(createElement(View, { style: () => ({
66
+ position: "fixed",
67
+ top: 0,
68
+ right: 0,
69
+ bottom: 0,
70
+ left: 0,
71
+ display: "flex",
72
+ alignItems: "center",
73
+ justifyContent: "center"
74
+ }) }, createElement(Pressable, {
75
+ style: scrimStyle,
76
+ label: "Close",
77
+ ...props.closeOnBackdrop !== false && props.onRequestClose ? { onPress: props.onRequestClose } : {}
78
+ }), createElement(FocusScope, {
79
+ role: props.role ?? "dialog",
80
+ ...props.onRequestClose ? { onEscape: props.onRequestClose } : {},
81
+ ...props.label !== void 0 ? { label: props.label } : {}
82
+ }, props.children)), props.mount !== void 0 ? { mount: props.mount } : void 0);
83
+ };
84
+ };
85
+ //#endregion
86
+ export { FocusScope, Modal };
87
+
88
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","names":["doc"],"sources":["../src/overlay.ts"],"sourcesContent":["/**\n * Atlas overlays — `Modal` + `FocusScope`, built on core's `portal`.\n *\n * `Modal` gates a portal by a reactive `visible`: on open it relocates a scrim + a focus-scoped\n * dialog to the renderer's overlay layer (above the tree); on close the gating region re-runs,\n * firing the portal's cleanup (unmount) and the FocusScope's focus-restore. `FocusScope` captures\n * the previously-focused element, auto-focuses its container, and restores focus on unmount —\n * DOM-feature-detected, so it no-ops on native/headless (the dialog markup + a11y still serialize).\n *\n * v1 scope: web is fully interactive (scrim dismiss, Escape, focus capture/restore). Native is\n * declarative — `role=\"dialog\"` + `aria-modal` are carried to the host; a true focus trap +\n * tab-cycling and back-button handling are a host follow-up (see the portal-modal ADR). Tab\n * cycling within the scope is also deferred (needs a descendant query).\n *\n * @module\n */\n\nimport { type Component, createElement, type MindeesNode, onCleanup, portal } from '@mindees/core'\nimport type { A11yProps, Role } from './a11y'\nimport type { Reactive } from './host'\nimport { Pressable, View } from './primitives'\nimport { flattenStyle, type StyleInput } from './style'\n\nfunction toAccessor<T>(value: Reactive<T>, fallback: T): () => T {\n return typeof value === 'function'\n ? (value as () => T)\n : () => (value === undefined ? fallback : value)\n}\n\n/** Props for {@link FocusScope}. */\nexport interface FocusScopeProps {\n readonly children?: MindeesNode\n /** Focus the scope container on mount (default true). */\n readonly autoFocus?: boolean\n /** Restore focus to the previously-focused element on unmount (default true). */\n readonly restoreFocus?: boolean\n /** Called when Escape is pressed inside the scope. */\n readonly onEscape?: () => void\n /** Dialog role (default `'dialog'`). */\n readonly role?: Role\n /** Accessible name (`aria-label`). */\n readonly label?: string\n /** Extra style on the scope container. */\n readonly style?: Reactive<StyleInput>\n}\n\n/** A focus-scoped, `aria-modal` container. Captures + restores focus on web; declarative elsewhere. */\nexport const FocusScope: Component<FocusScopeProps> = (props) => {\n let restore: (() => void) | null = null\n const captureAndFocus = (host: unknown): void => {\n if (typeof document === 'undefined') return // native/headless: declarative only\n const node = host as { focus?: () => void } | null\n const doc = document as unknown as { activeElement?: { focus?: () => void } | null }\n const previous = doc.activeElement ?? null\n if (props.restoreFocus !== false && previous && typeof previous.focus === 'function') {\n restore = () => previous.focus?.()\n }\n if (props.autoFocus !== false && node && typeof node.focus === 'function') {\n // Defer: `ref` fires before the portal subtree is connected to the document, so a synchronous\n // focus() would no-op. By the microtask the subtree is mounted; skip if it closed first.\n queueMicrotask(() => {\n if ((node as { isConnected?: boolean }).isConnected) node.focus?.()\n })\n }\n }\n onCleanup(() => restore?.())\n\n const callerStyle = props.style\n const style: Reactive<StyleInput> = () =>\n flattenStyle([\n { position: 'relative' },\n typeof callerStyle === 'function' ? callerStyle() : (callerStyle ?? {}),\n ])\n\n const hostProps: Record<string, unknown> = {\n role: props.role ?? 'dialog',\n 'aria-modal': 'true',\n tabindex: -1,\n ref: captureAndFocus,\n style,\n }\n if (props.label !== undefined) hostProps['aria-label'] = props.label\n if (props.onEscape !== undefined) {\n hostProps.onKeyDown = (e: unknown) => {\n if ((e as { key?: string }).key === 'Escape') props.onEscape?.()\n }\n }\n // A RAW host `view` (not the curated View primitive) so ref/tabindex/onKeyDown/aria-modal pass through.\n return createElement('view', hostProps, props.children)\n}\n\n/** Props for {@link Modal}. */\nexport interface ModalProps extends A11yProps {\n /** Whether the modal is open (static or reactive). */\n readonly visible: Reactive<boolean>\n /** Requested close (scrim press or Escape). */\n readonly onRequestClose?: () => void\n readonly children?: MindeesNode\n /** Close when the scrim is pressed (default true). */\n readonly closeOnBackdrop?: boolean\n /** Extra style merged into the scrim. */\n readonly backdrop?: Reactive<StyleInput>\n /** Explicit overlay host target (else the backend's overlay layer). */\n readonly mount?: unknown\n}\n\n/** A portal-backed modal dialog: a dismissable scrim + a focus-scoped dialog above the tree. */\nexport const Modal: Component<ModalProps> = (props) => {\n const isVisible = toAccessor(props.visible, false)\n return () => {\n if (!isVisible()) return null\n\n const scrimStyle = (): StyleInput =>\n flattenStyle([\n {\n position: 'absolute',\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n backgroundColor: 'rgba(0,0,0,0.5)',\n },\n typeof props.backdrop === 'function' ? props.backdrop() : (props.backdrop ?? {}),\n ])\n const scrim = createElement(Pressable, {\n style: scrimStyle,\n label: 'Close',\n ...(props.closeOnBackdrop !== false && props.onRequestClose\n ? { onPress: props.onRequestClose }\n : {}),\n })\n\n const dialog = createElement(\n FocusScope,\n {\n role: props.role ?? 'dialog',\n ...(props.onRequestClose ? { onEscape: props.onRequestClose } : {}),\n ...(props.label !== undefined ? { label: props.label } : {}),\n },\n props.children,\n )\n\n // A full-screen flex-center container holds the scrim (behind) + the dialog (on top, centered).\n const container = createElement(\n View,\n {\n style: () => ({\n position: 'fixed',\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }),\n },\n scrim,\n dialog,\n )\n return portal(container, props.mount !== undefined ? { mount: props.mount } : undefined)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,SAAS,WAAc,OAAoB,UAAsB;CAC/D,OAAO,OAAO,UAAU,aACnB,cACM,UAAU,KAAA,IAAY,WAAW;AAC9C;;AAoBA,MAAa,cAA0C,UAAU;CAC/D,IAAI,UAA+B;CACnC,MAAM,mBAAmB,SAAwB;EAC/C,IAAI,OAAO,aAAa,aAAa;EACrC,MAAM,OAAO;EAEb,MAAM,WAAWA,SAAI,iBAAiB;EACtC,IAAI,MAAM,iBAAiB,SAAS,YAAY,OAAO,SAAS,UAAU,YACxE,gBAAgB,SAAS,QAAQ;EAEnC,IAAI,MAAM,cAAc,SAAS,QAAQ,OAAO,KAAK,UAAU,YAG7D,qBAAqB;GACnB,IAAK,KAAmC,aAAa,KAAK,QAAQ;EACpE,CAAC;CAEL;CACA,gBAAgB,UAAU,CAAC;CAE3B,MAAM,cAAc,MAAM;CAC1B,MAAM,cACJ,aAAa,CACX,EAAE,UAAU,WAAW,GACvB,OAAO,gBAAgB,aAAa,YAAY,IAAK,eAAe,CAAC,CACvE,CAAC;CAEH,MAAM,YAAqC;EACzC,MAAM,MAAM,QAAQ;EACpB,cAAc;EACd,UAAU;EACV,KAAK;EACL;CACF;CACA,IAAI,MAAM,UAAU,KAAA,GAAW,UAAU,gBAAgB,MAAM;CAC/D,IAAI,MAAM,aAAa,KAAA,GACrB,UAAU,aAAa,MAAe;EACpC,IAAK,EAAuB,QAAQ,UAAU,MAAM,WAAW;CACjE;CAGF,OAAO,cAAc,QAAQ,WAAW,MAAM,QAAQ;AACxD;;AAkBA,MAAa,SAAgC,UAAU;CACrD,MAAM,YAAY,WAAW,MAAM,SAAS,KAAK;CACjD,aAAa;EACX,IAAI,CAAC,UAAU,GAAG,OAAO;EAEzB,MAAM,mBACJ,aAAa,CACX;GACE,UAAU;GACV,KAAK;GACL,OAAO;GACP,QAAQ;GACR,MAAM;GACN,iBAAiB;EACnB,GACA,OAAO,MAAM,aAAa,aAAa,MAAM,SAAS,IAAK,MAAM,YAAY,CAAC,CAChF,CAAC;EAqCH,OAAO,OAjBW,cAChB,MACA,EACE,cAAc;GACZ,UAAU;GACV,KAAK;GACL,OAAO;GACP,QAAQ;GACR,MAAM;GACN,SAAS;GACT,YAAY;GACZ,gBAAgB;EAClB,GACF,GAhCY,cAAc,WAAW;GACrC,OAAO;GACP,OAAO;GACP,GAAI,MAAM,oBAAoB,SAAS,MAAM,iBACzC,EAAE,SAAS,MAAM,eAAe,IAChC,CAAC;EACP,CA2BM,GAzBS,cACb,YACA;GACE,MAAM,MAAM,QAAQ;GACpB,GAAI,MAAM,iBAAiB,EAAE,UAAU,MAAM,eAAe,IAAI,CAAC;GACjE,GAAI,MAAM,UAAU,KAAA,IAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;EAC5D,GACA,MAAM,QAmBD,CAEe,GAAG,MAAM,UAAU,KAAA,IAAY,EAAE,OAAO,MAAM,MAAM,IAAI,KAAA,CAAS;CACzF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindees/atlas",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "MindeesNative Atlas - accessible, signals-native UI primitives + a virtualized recycling list. Renderer-agnostic (web real, native research track).",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
@@ -20,6 +20,10 @@
20
20
  "./list": {
21
21
  "types": "./dist/list.d.ts",
22
22
  "import": "./dist/list.js"
23
+ },
24
+ "./for": {
25
+ "types": "./dist/for.d.ts",
26
+ "import": "./dist/for.js"
23
27
  }
24
28
  },
25
29
  "publishConfig": {
@@ -31,11 +35,11 @@
31
35
  "directory": "packages/atlas"
32
36
  },
33
37
  "dependencies": {
34
- "@mindees/core": "0.2.0"
38
+ "@mindees/core": "0.4.0"
35
39
  },
36
40
  "devDependencies": {
37
41
  "happy-dom": "20.9.0",
38
- "@mindees/renderer": "0.2.0"
42
+ "@mindees/renderer": "0.4.0"
39
43
  },
40
44
  "scripts": {
41
45
  "build": "tsdown",