@mindees/atlas 0.31.0 → 0.31.1

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/index.d.ts CHANGED
@@ -19,7 +19,7 @@ import { Maturity, NotImplementedError, PackageInfo, notImplemented } from "@min
19
19
  /** The npm package name. */
20
20
  declare const name = "@mindees/atlas";
21
21
  /** The package version. All `@mindees/*` packages share one locked version line. */
22
- declare const VERSION = "0.31.0";
22
+ declare const VERSION = "0.31.1";
23
23
  /** Current maturity of this package. See the repository `STATUS.md`. */
24
24
  declare const maturity: Maturity;
25
25
  /**
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import { NotImplementedError, notImplemented } from "@mindees/core";
18
18
  /** The npm package name. */
19
19
  const name = "@mindees/atlas";
20
20
  /** The package version. All `@mindees/*` packages share one locked version line. */
21
- const VERSION = "0.31.0";
21
+ const VERSION = "0.31.1";
22
22
  /** Current maturity of this package. See the repository `STATUS.md`. */
23
23
  const maturity = "experimental";
24
24
  /**
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 design-token theming (`useTheme`/`tokens`, on the main entry).\n * 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.31.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 {\n type A11yProps,\n type A11yState,\n type Announce,\n announce,\n type Role,\n toA11yProps,\n} from './a11y'\nexport {\n Accordion,\n type AccordionProps,\n type AccordionSection,\n ActivityIndicator,\n type ActivityIndicatorProps,\n Avatar,\n type AvatarProps,\n Badge,\n type BadgeProps,\n Card,\n type CardProps,\n Checkbox,\n type CheckboxProps,\n Chip,\n type ChipProps,\n Divider,\n type DividerProps,\n KeyboardAvoidingView,\n type KeyboardAvoidingViewProps,\n ProgressBar,\n type ProgressBarProps,\n RadioGroup,\n type RadioGroupProps,\n type RadioOption,\n SafeAreaView,\n type SafeAreaViewProps,\n type Segment,\n SegmentedControl,\n type SegmentedControlProps,\n Skeleton,\n type SkeletonProps,\n Stepper,\n type StepperProps,\n Switch,\n type SwitchProps,\n type TabItem,\n Tabs,\n type TabsProps,\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 useReducedMotion,\n useSafeAreaInsets,\n useWindowDimensions,\n type WindowDimensions,\n} from './environment'\nexport { ErrorBoundary, type ErrorBoundaryProps } from './error-boundary'\nexport { type Field, type FormApi, type UseFormOptions, useForm } from './form'\nexport { type AttachableGesture, GestureView, type GestureViewProps } from './gesture'\nexport {\n type AsyncState,\n type Counter,\n type PersistentSignalOptions,\n type SignalStorage,\n type Toggle,\n useAsync,\n useCounter,\n useDebounce,\n useInterval,\n usePersistentSignal,\n usePrevious,\n useReducer,\n useTimeout,\n useToggle,\n} from './hooks'\nexport { type BaseProps, type Reactive, resolveStyle, toHostProps } from './host'\nexport { animateTo, motion } from './motion'\nexport {\n FocusScope,\n type FocusScopeProps,\n Modal,\n type ModalProps,\n Toast,\n type ToastProps,\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 { Show, type ShowProps } from './show'\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 { connectWebEnvironment, type WebEnvWindow } from './web-environment'\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 design-token theming (`useTheme`/`tokens`, on the main entry).\n * 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.31.1'\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 {\n type A11yProps,\n type A11yState,\n type Announce,\n announce,\n type Role,\n toA11yProps,\n} from './a11y'\nexport {\n Accordion,\n type AccordionProps,\n type AccordionSection,\n ActivityIndicator,\n type ActivityIndicatorProps,\n Avatar,\n type AvatarProps,\n Badge,\n type BadgeProps,\n Card,\n type CardProps,\n Checkbox,\n type CheckboxProps,\n Chip,\n type ChipProps,\n Divider,\n type DividerProps,\n KeyboardAvoidingView,\n type KeyboardAvoidingViewProps,\n ProgressBar,\n type ProgressBarProps,\n RadioGroup,\n type RadioGroupProps,\n type RadioOption,\n SafeAreaView,\n type SafeAreaViewProps,\n type Segment,\n SegmentedControl,\n type SegmentedControlProps,\n Skeleton,\n type SkeletonProps,\n Stepper,\n type StepperProps,\n Switch,\n type SwitchProps,\n type TabItem,\n Tabs,\n type TabsProps,\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 useReducedMotion,\n useSafeAreaInsets,\n useWindowDimensions,\n type WindowDimensions,\n} from './environment'\nexport { ErrorBoundary, type ErrorBoundaryProps } from './error-boundary'\nexport { type Field, type FormApi, type UseFormOptions, useForm } from './form'\nexport { type AttachableGesture, GestureView, type GestureViewProps } from './gesture'\nexport {\n type AsyncState,\n type Counter,\n type PersistentSignalOptions,\n type SignalStorage,\n type Toggle,\n useAsync,\n useCounter,\n useDebounce,\n useInterval,\n usePersistentSignal,\n usePrevious,\n useReducer,\n useTimeout,\n useToggle,\n} from './hooks'\nexport { type BaseProps, type Reactive, resolveStyle, toHostProps } from './host'\nexport { animateTo, motion } from './motion'\nexport {\n FocusScope,\n type FocusScopeProps,\n Modal,\n type ModalProps,\n Toast,\n type ToastProps,\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 { Show, type ShowProps } from './show'\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 { connectWebEnvironment, type WebEnvWindow } from './web-environment'\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"}
package/dist/tab.d.ts CHANGED
@@ -19,14 +19,17 @@ interface TabDef {
19
19
  */
20
20
  readonly component: Component<RouteComponentProps>;
21
21
  }
22
- /** Options for {@link createTabNavigator}. */
23
- interface TabNavigatorOptions {
24
- readonly tabs: readonly TabDef[];
25
- /** Tab-bar edge (default `'bottom'`). */
22
+ /** Per-render presentation overrides — mirror `createStackNavigator`'s per-render ergonomics. */
23
+ interface TabNavigatorProps {
24
+ /** Tab-bar edge (default `'bottom'`). Overrides the factory default for this render. */
26
25
  readonly tabBarPosition?: 'top' | 'bottom';
27
- /** Extra style merged into the tab bar. */
26
+ /** Extra style merged into the tab bar. Overrides the factory default for this render. */
28
27
  readonly tabBarStyle?: Reactive<StyleInput>;
29
28
  }
29
+ /** Options for {@link createTabNavigator}. The `tabs` list is required at factory time. */
30
+ interface TabNavigatorOptions extends TabNavigatorProps {
31
+ readonly tabs: readonly TabDef[];
32
+ }
30
33
  /**
31
34
  * Create a tab navigator {@link Component} bound to `router`. Render it via `createElement` (so the
32
35
  * renderer owns its reactive scope and disposes it on unmount).
@@ -39,7 +42,7 @@ interface TabNavigatorOptions {
39
42
  * ],
40
43
  * })
41
44
  */
42
- declare function createTabNavigator(router: Router, options: TabNavigatorOptions): Component<Record<string, never>>;
45
+ declare function createTabNavigator(router: Router, options: TabNavigatorOptions): Component<TabNavigatorProps>;
43
46
  //#endregion
44
- export { TabDef, TabNavigatorOptions, createTabNavigator };
47
+ export { TabDef, TabNavigatorOptions, TabNavigatorProps, createTabNavigator };
45
48
  //# sourceMappingURL=tab.d.ts.map
package/dist/tab.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tab.d.ts","names":[],"sources":["../src/tab.ts"],"mappings":";;;;;;;UAwBiB,MAAA;EAiBS;EAAA,SAff,IAAA;EAmBc;EAAA,SAjBd,KAAA;EAiBsB;;;;;;;EAAA,SATtB,SAAA,EAAW,SAAS,CAAC,mBAAA;AAAA;AA4BhC;AAAA,UAxBiB,mBAAA;EAAA,SACN,IAAA,WAAe,MAAA;EAwBhB;EAAA,SAtBC,cAAA;EAwBE;EAAA,SAtBF,WAAA,GAAc,QAAA,CAAS,UAAA;AAAA;;;;;;;;;AAsBf;;;;iBAHH,kBAAA,CACd,MAAA,EAAQ,MAAA,EACR,OAAA,EAAS,mBAAA,GACR,SAAA,CAAU,MAAA"}
1
+ {"version":3,"file":"tab.d.ts","names":[],"sources":["../src/tab.ts"],"mappings":";;;;;;;UAwBiB,MAAA;EAoBgB;EAAA,SAlBtB,IAAA;EAkBA;EAAA,SAhBA,KAAA;EAgBuB;;AAAU;AAI5C;;;;EAJkC,SARvB,SAAA,EAAW,SAAS,CAAC,mBAAA;AAAA;;UAIf,iBAAA;EASe;EAAA,SAPrB,cAAA;EA0BuB;EAAA,SAxBvB,WAAA,GAAc,QAAQ,CAAC,UAAA;AAAA;;UAIjB,mBAAA,SAA4B,iBAAiB;EAAA,SACnD,IAAA,WAAe,MAAA;AAAA;;;;;;;;;AAsBI;;;;iBAHd,kBAAA,CACd,MAAA,EAAQ,MAAA,EACR,OAAA,EAAS,mBAAA,GACR,SAAA,CAAU,iBAAA"}
package/dist/tab.js CHANGED
@@ -34,8 +34,9 @@ const styleFn = (extra, base) => {
34
34
  */
35
35
  function createTabNavigator(router, options) {
36
36
  const tabs = options.tabs;
37
- const position = options.tabBarPosition ?? "bottom";
38
- return () => {
37
+ return (props = {}) => {
38
+ const position = props.tabBarPosition ?? options.tabBarPosition ?? "bottom";
39
+ const tabBarStyle = props.tabBarStyle ?? options.tabBarStyle;
39
40
  const activeIndex = () => {
40
41
  const path = router.location().pathname;
41
42
  let best = -1;
@@ -77,7 +78,7 @@ function createTabNavigator(router, options) {
77
78
  }) : null)));
78
79
  const bar = createElement(View, {
79
80
  role: "tablist",
80
- style: styleFn(options.tabBarStyle, {
81
+ style: styleFn(tabBarStyle, {
81
82
  display: "flex",
82
83
  flexDirection: "row"
83
84
  })
package/dist/tab.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tab.js","names":[],"sources":["../src/tab.ts"],"sourcesContent":["/**\n * Atlas `createTabNavigator` — tab navigation over the Quantum router.\n *\n * Each tab owns a route path. The active tab is DERIVED from the current URL (longest matching tab path),\n * so deep-links and back/forward Just Work; tapping a tab navigates. Every tab's screen stays MOUNTED, so\n * its state (scroll position, form input, in-flight data) is preserved across switches — only visibility\n * toggles. Full ARIA `tablist`/`tab`/`tabpanel` semantics (an inactive panel is `display:none`, which also\n * removes it from the a11y tree and tab order). No new core/router surface.\n *\n * v1 scope: web is real; screens mount eagerly and keep state (lazy mounting is a follow-up). Native\n * carries the same markup (interpretation is a host concern).\n *\n * @module\n */\n\nimport { type Component, createElement, effect, signal } from '@mindees/core'\nimport type { LoaderData, RouteComponentProps, Router } from '@mindees/router'\nimport type { Reactive } from './host'\nimport { Pressable, Text, View } from './primitives'\nimport { flattenStyle, type StyleInput } from './style'\n\nconst IDLE_LOADER: LoaderData = Object.freeze({ status: 'idle' })\n\n/** One tab in a {@link createTabNavigator}. */\nexport interface TabDef {\n /** The route path this tab activates + navigates to (e.g. `/home`). */\n readonly path: string\n /** Tab-bar label (also the tab's accessible name). */\n readonly label: string\n /**\n * The screen component for this tab. Receives the full {@link RouteComponentProps} contract (`router`,\n * reactive `params`/`search`, and `data` for the active route's loader) — the same props\n * `createRouterView` passes — so a tab screen reads params + loader data the standard way. (A plain\n * `() => …` component that ignores props is fine.) NOTE: nested routes *under* a tab are not auto-rendered\n * into an outlet — a tab whose route has children should render its own `createRouterView` for them.\n */\n readonly component: Component<RouteComponentProps>\n}\n\n/** Options for {@link createTabNavigator}. */\nexport interface TabNavigatorOptions {\n readonly tabs: readonly TabDef[]\n /** Tab-bar edge (default `'bottom'`). */\n readonly tabBarPosition?: 'top' | 'bottom'\n /** Extra style merged into the tab bar. */\n readonly tabBarStyle?: Reactive<StyleInput>\n}\n\nconst styleFn = (extra: Reactive<StyleInput> | undefined, base: StyleInput): (() => StyleInput) => {\n return () => flattenStyle([base, typeof extra === 'function' ? extra() : (extra ?? {})])\n}\n\n/**\n * Create a tab navigator {@link Component} bound to `router`. Render it via `createElement` (so the\n * renderer owns its reactive scope and disposes it on unmount).\n *\n * @example\n * const Tabs = createTabNavigator(router, {\n * tabs: [\n * { path: '/home', label: 'Home', component: Home },\n * { path: '/settings', label: 'Settings', component: Settings },\n * ],\n * })\n */\nexport function createTabNavigator(\n router: Router,\n options: TabNavigatorOptions,\n): Component<Record<string, never>> {\n const tabs = options.tabs\n const position = options.tabBarPosition ?? 'bottom'\n\n return () => {\n // Active tab = the LONGEST tab path that prefixes the current pathname (deep-link + nested-route aware).\n // Returns -1 when the URL belongs to NO tab — better to select/show nothing than a misleading tab 0.\n const activeIndex = (): number => {\n const path = router.location().pathname\n let best = -1\n let bestLen = -1\n tabs.forEach((t, i) => {\n if ((path === t.path || path.startsWith(`${t.path}/`)) && t.path.length > bestLen) {\n best = i\n bestLen = t.path.length\n }\n })\n return best\n }\n\n // Lazy + keep-alive (RN parity): a tab's screen mounts on its FIRST activation and stays mounted\n // thereafter, so an unvisited tab's loaders/effects never run, and a visited tab keeps its state.\n const visited = tabs.map(() => signal(false))\n effect(() => {\n visited[activeIndex()]?.set(true)\n })\n\n // One panel per tab; only the active one is shown (`display:none` also drops inactive panels from the\n // a11y tree + tab order). The screen is mounted lazily (once visited) and never unmounted.\n const panels = tabs.map((t, i) =>\n createElement(\n View,\n {\n role: 'tabpanel',\n style: () => ({\n display: activeIndex() === i ? 'flex' : 'none',\n flexDirection: 'column',\n flex: 1,\n minHeight: 0,\n }),\n },\n // Thread the router screen contract so a tab screen reads params/search/loader-data the standard\n // way (mirrors createRouterView). `data` resolves the active leaf match; `children` is null (a tab\n // screen with nested routes renders its own createRouterView — see TabDef.component).\n () =>\n visited[i]?.()\n ? createElement(t.component, {\n router,\n params: router.params,\n search: router.search,\n data: () => {\n const m = router.match()\n return m ? router.loaderData(m) : IDLE_LOADER\n },\n children: null,\n })\n : null,\n ),\n )\n const panelArea = createElement(\n View,\n { style: () => ({ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }) },\n ...panels,\n )\n\n const bar = createElement(\n View,\n {\n role: 'tablist',\n style: styleFn(options.tabBarStyle, { display: 'flex', flexDirection: 'row' }),\n },\n ...tabs.map((t, i) =>\n createElement(\n Pressable,\n {\n role: 'tab',\n label: t.label,\n state: () => ({ selected: activeIndex() === i }),\n style: () => ({\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 12,\n }),\n onPress: () => {\n if (activeIndex() !== i) router.navigate(t.path)\n },\n },\n createElement(Text, {}, t.label),\n ),\n ),\n )\n\n return createElement(\n View,\n { style: () => ({ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }) },\n ...(position === 'top' ? [bar, panelArea] : [panelArea, bar]),\n )\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,MAAM,cAA0B,OAAO,OAAO,EAAE,QAAQ,OAAO,CAAC;AA2BhE,MAAM,WAAW,OAAyC,SAAyC;CACjG,aAAa,aAAa,CAAC,MAAM,OAAO,UAAU,aAAa,MAAM,IAAK,SAAS,CAAC,CAAE,CAAC;AACzF;;;;;;;;;;;;;AAcA,SAAgB,mBACd,QACA,SACkC;CAClC,MAAM,OAAO,QAAQ;CACrB,MAAM,WAAW,QAAQ,kBAAkB;CAE3C,aAAa;EAGX,MAAM,oBAA4B;GAChC,MAAM,OAAO,OAAO,SAAS,EAAE;GAC/B,IAAI,OAAO;GACX,IAAI,UAAU;GACd,KAAK,SAAS,GAAG,MAAM;IACrB,KAAK,SAAS,EAAE,QAAQ,KAAK,WAAW,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAS,SAAS;KACjF,OAAO;KACP,UAAU,EAAE,KAAK;IACnB;GACF,CAAC;GACD,OAAO;EACT;EAIA,MAAM,UAAU,KAAK,UAAU,OAAO,KAAK,CAAC;EAC5C,aAAa;GACX,QAAQ,YAAY,IAAI,IAAI,IAAI;EAClC,CAAC;EAkCD,MAAM,YAAY,cAChB,MACA,EAAE,cAAc;GAAE,SAAS;GAAQ,eAAe;GAAU,MAAM;GAAG,WAAW;EAAE,GAAG,GACrF,GAjCa,KAAK,KAAK,GAAG,MAC1B,cACE,MACA;GACE,MAAM;GACN,cAAc;IACZ,SAAS,YAAY,MAAM,IAAI,SAAS;IACxC,eAAe;IACf,MAAM;IACN,WAAW;GACb;EACF,SAKE,QAAQ,KAAK,IACT,cAAc,EAAE,WAAW;GACzB;GACA,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,YAAY;IACV,MAAM,IAAI,OAAO,MAAM;IACvB,OAAO,IAAI,OAAO,WAAW,CAAC,IAAI;GACpC;GACA,UAAU;EACZ,CAAC,IACD,IACR,CAKQ,CACV;EAEA,MAAM,MAAM,cACV,MACA;GACE,MAAM;GACN,OAAO,QAAQ,QAAQ,aAAa;IAAE,SAAS;IAAQ,eAAe;GAAM,CAAC;EAC/E,GACA,GAAG,KAAK,KAAK,GAAG,MACd,cACE,WACA;GACE,MAAM;GACN,OAAO,EAAE;GACT,cAAc,EAAE,UAAU,YAAY,MAAM,EAAE;GAC9C,cAAc;IACZ,MAAM;IACN,SAAS;IACT,YAAY;IACZ,gBAAgB;IAChB,SAAS;GACX;GACA,eAAe;IACb,IAAI,YAAY,MAAM,GAAG,OAAO,SAAS,EAAE,IAAI;GACjD;EACF,GACA,cAAc,MAAM,CAAC,GAAG,EAAE,KAAK,CACjC,CACF,CACF;EAEA,OAAO,cACL,MACA,EAAE,cAAc;GAAE,SAAS;GAAQ,eAAe;GAAU,MAAM;GAAG,WAAW;EAAE,GAAG,GACrF,GAAI,aAAa,QAAQ,CAAC,KAAK,SAAS,IAAI,CAAC,WAAW,GAAG,CAC7D;CACF;AACF"}
1
+ {"version":3,"file":"tab.js","names":[],"sources":["../src/tab.ts"],"sourcesContent":["/**\n * Atlas `createTabNavigator` — tab navigation over the Quantum router.\n *\n * Each tab owns a route path. The active tab is DERIVED from the current URL (longest matching tab path),\n * so deep-links and back/forward Just Work; tapping a tab navigates. Every tab's screen stays MOUNTED, so\n * its state (scroll position, form input, in-flight data) is preserved across switches — only visibility\n * toggles. Full ARIA `tablist`/`tab`/`tabpanel` semantics (an inactive panel is `display:none`, which also\n * removes it from the a11y tree and tab order). No new core/router surface.\n *\n * v1 scope: web is real; screens mount eagerly and keep state (lazy mounting is a follow-up). Native\n * carries the same markup (interpretation is a host concern).\n *\n * @module\n */\n\nimport { type Component, createElement, effect, signal } from '@mindees/core'\nimport type { LoaderData, RouteComponentProps, Router } from '@mindees/router'\nimport type { Reactive } from './host'\nimport { Pressable, Text, View } from './primitives'\nimport { flattenStyle, type StyleInput } from './style'\n\nconst IDLE_LOADER: LoaderData = Object.freeze({ status: 'idle' })\n\n/** One tab in a {@link createTabNavigator}. */\nexport interface TabDef {\n /** The route path this tab activates + navigates to (e.g. `/home`). */\n readonly path: string\n /** Tab-bar label (also the tab's accessible name). */\n readonly label: string\n /**\n * The screen component for this tab. Receives the full {@link RouteComponentProps} contract (`router`,\n * reactive `params`/`search`, and `data` for the active route's loader) — the same props\n * `createRouterView` passes — so a tab screen reads params + loader data the standard way. (A plain\n * `() => …` component that ignores props is fine.) NOTE: nested routes *under* a tab are not auto-rendered\n * into an outlet — a tab whose route has children should render its own `createRouterView` for them.\n */\n readonly component: Component<RouteComponentProps>\n}\n\n/** Per-render presentation overrides — mirror `createStackNavigator`'s per-render ergonomics. */\nexport interface TabNavigatorProps {\n /** Tab-bar edge (default `'bottom'`). Overrides the factory default for this render. */\n readonly tabBarPosition?: 'top' | 'bottom'\n /** Extra style merged into the tab bar. Overrides the factory default for this render. */\n readonly tabBarStyle?: Reactive<StyleInput>\n}\n\n/** Options for {@link createTabNavigator}. The `tabs` list is required at factory time. */\nexport interface TabNavigatorOptions extends TabNavigatorProps {\n readonly tabs: readonly TabDef[]\n}\n\nconst styleFn = (extra: Reactive<StyleInput> | undefined, base: StyleInput): (() => StyleInput) => {\n return () => flattenStyle([base, typeof extra === 'function' ? extra() : (extra ?? {})])\n}\n\n/**\n * Create a tab navigator {@link Component} bound to `router`. Render it via `createElement` (so the\n * renderer owns its reactive scope and disposes it on unmount).\n *\n * @example\n * const Tabs = createTabNavigator(router, {\n * tabs: [\n * { path: '/home', label: 'Home', component: Home },\n * { path: '/settings', label: 'Settings', component: Settings },\n * ],\n * })\n */\nexport function createTabNavigator(\n router: Router,\n options: TabNavigatorOptions,\n): Component<TabNavigatorProps> {\n const tabs = options.tabs\n\n return (props = {}) => {\n // Per-render overrides win over the factory defaults (parity with createStackNavigator).\n const position = props.tabBarPosition ?? options.tabBarPosition ?? 'bottom'\n const tabBarStyle = props.tabBarStyle ?? options.tabBarStyle\n // Active tab = the LONGEST tab path that prefixes the current pathname (deep-link + nested-route aware).\n // Returns -1 when the URL belongs to NO tab — better to select/show nothing than a misleading tab 0.\n const activeIndex = (): number => {\n const path = router.location().pathname\n let best = -1\n let bestLen = -1\n tabs.forEach((t, i) => {\n if ((path === t.path || path.startsWith(`${t.path}/`)) && t.path.length > bestLen) {\n best = i\n bestLen = t.path.length\n }\n })\n return best\n }\n\n // Lazy + keep-alive (RN parity): a tab's screen mounts on its FIRST activation and stays mounted\n // thereafter, so an unvisited tab's loaders/effects never run, and a visited tab keeps its state.\n const visited = tabs.map(() => signal(false))\n effect(() => {\n visited[activeIndex()]?.set(true)\n })\n\n // One panel per tab; only the active one is shown (`display:none` also drops inactive panels from the\n // a11y tree + tab order). The screen is mounted lazily (once visited) and never unmounted.\n const panels = tabs.map((t, i) =>\n createElement(\n View,\n {\n role: 'tabpanel',\n style: () => ({\n display: activeIndex() === i ? 'flex' : 'none',\n flexDirection: 'column',\n flex: 1,\n minHeight: 0,\n }),\n },\n // Thread the router screen contract so a tab screen reads params/search/loader-data the standard\n // way (mirrors createRouterView). `data` resolves the active leaf match; `children` is null (a tab\n // screen with nested routes renders its own createRouterView — see TabDef.component).\n () =>\n visited[i]?.()\n ? createElement(t.component, {\n router,\n params: router.params,\n search: router.search,\n data: () => {\n const m = router.match()\n return m ? router.loaderData(m) : IDLE_LOADER\n },\n children: null,\n })\n : null,\n ),\n )\n const panelArea = createElement(\n View,\n { style: () => ({ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }) },\n ...panels,\n )\n\n const bar = createElement(\n View,\n {\n role: 'tablist',\n style: styleFn(tabBarStyle, { display: 'flex', flexDirection: 'row' }),\n },\n ...tabs.map((t, i) =>\n createElement(\n Pressable,\n {\n role: 'tab',\n label: t.label,\n state: () => ({ selected: activeIndex() === i }),\n style: () => ({\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 12,\n }),\n onPress: () => {\n if (activeIndex() !== i) router.navigate(t.path)\n },\n },\n createElement(Text, {}, t.label),\n ),\n ),\n )\n\n return createElement(\n View,\n { style: () => ({ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }) },\n ...(position === 'top' ? [bar, panelArea] : [panelArea, bar]),\n )\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,MAAM,cAA0B,OAAO,OAAO,EAAE,QAAQ,OAAO,CAAC;AA+BhE,MAAM,WAAW,OAAyC,SAAyC;CACjG,aAAa,aAAa,CAAC,MAAM,OAAO,UAAU,aAAa,MAAM,IAAK,SAAS,CAAC,CAAE,CAAC;AACzF;;;;;;;;;;;;;AAcA,SAAgB,mBACd,QACA,SAC8B;CAC9B,MAAM,OAAO,QAAQ;CAErB,QAAQ,QAAQ,CAAC,MAAM;EAErB,MAAM,WAAW,MAAM,kBAAkB,QAAQ,kBAAkB;EACnE,MAAM,cAAc,MAAM,eAAe,QAAQ;EAGjD,MAAM,oBAA4B;GAChC,MAAM,OAAO,OAAO,SAAS,EAAE;GAC/B,IAAI,OAAO;GACX,IAAI,UAAU;GACd,KAAK,SAAS,GAAG,MAAM;IACrB,KAAK,SAAS,EAAE,QAAQ,KAAK,WAAW,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAS,SAAS;KACjF,OAAO;KACP,UAAU,EAAE,KAAK;IACnB;GACF,CAAC;GACD,OAAO;EACT;EAIA,MAAM,UAAU,KAAK,UAAU,OAAO,KAAK,CAAC;EAC5C,aAAa;GACX,QAAQ,YAAY,IAAI,IAAI,IAAI;EAClC,CAAC;EAkCD,MAAM,YAAY,cAChB,MACA,EAAE,cAAc;GAAE,SAAS;GAAQ,eAAe;GAAU,MAAM;GAAG,WAAW;EAAE,GAAG,GACrF,GAjCa,KAAK,KAAK,GAAG,MAC1B,cACE,MACA;GACE,MAAM;GACN,cAAc;IACZ,SAAS,YAAY,MAAM,IAAI,SAAS;IACxC,eAAe;IACf,MAAM;IACN,WAAW;GACb;EACF,SAKE,QAAQ,KAAK,IACT,cAAc,EAAE,WAAW;GACzB;GACA,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,YAAY;IACV,MAAM,IAAI,OAAO,MAAM;IACvB,OAAO,IAAI,OAAO,WAAW,CAAC,IAAI;GACpC;GACA,UAAU;EACZ,CAAC,IACD,IACR,CAKQ,CACV;EAEA,MAAM,MAAM,cACV,MACA;GACE,MAAM;GACN,OAAO,QAAQ,aAAa;IAAE,SAAS;IAAQ,eAAe;GAAM,CAAC;EACvE,GACA,GAAG,KAAK,KAAK,GAAG,MACd,cACE,WACA;GACE,MAAM;GACN,OAAO,EAAE;GACT,cAAc,EAAE,UAAU,YAAY,MAAM,EAAE;GAC9C,cAAc;IACZ,MAAM;IACN,SAAS;IACT,YAAY;IACZ,gBAAgB;IAChB,SAAS;GACX;GACA,eAAe;IACb,IAAI,YAAY,MAAM,GAAG,OAAO,SAAS,EAAE,IAAI;GACjD;EACF,GACA,cAAc,MAAM,CAAC,GAAG,EAAE,KAAK,CACjC,CACF,CACF;EAEA,OAAO,cACL,MACA,EAAE,cAAc;GAAE,SAAS;GAAQ,eAAe;GAAU,MAAM;GAAG,WAAW;EAAE,GAAG,GACrF,GAAI,aAAa,QAAQ,CAAC,KAAK,SAAS,IAAI,CAAC,WAAW,GAAG,CAC7D;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindees/atlas",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
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",
@@ -42,12 +42,12 @@
42
42
  "directory": "packages/atlas"
43
43
  },
44
44
  "dependencies": {
45
- "@mindees/core": "0.31.0",
46
- "@mindees/router": "0.31.0"
45
+ "@mindees/core": "0.31.1",
46
+ "@mindees/router": "0.31.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "happy-dom": "20.9.0",
50
- "@mindees/renderer": "0.31.0"
50
+ "@mindees/renderer": "0.31.1"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsdown",