@apliteni/apliteni-ui 0.55.0 → 0.56.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.55.0",
3
+ "version": "0.56.1",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -385,8 +385,20 @@ uses its normal opening focus.
385
385
 
386
386
  `Timeline` displays a record’s events in the order provided. Pass events from oldest
387
387
  to newest, with stable IDs, machine-readable `dateTime` values, and formatted
388
- `timestamp` text.
389
- Descriptions should say what changed and to where.
388
+ `timestamp` text. Place Timeline on a page or card reading surface, never a grey
389
+ fill; the standalone stories compose it inside `Card`.
390
+ Descriptions should say what changed and to where. Optional `kind` accepts
391
+ `person`, `rule`, or `reversal` (`TimelineEventKind`); these show the kit’s user,
392
+ bolt, or refresh glyph in a 20px ring. The last event is newest and its ring is
393
+ filled, using the danger colour for a reversal. Events without a kind keep their
394
+ dot. Mixed histories mute older dots; older reversal glyphs keep danger ink.
395
+
396
+ Initial history stays still, including history loaded into an empty list.
397
+ New IDs added to a non-empty history enter with the kit’s slide-up motion (400ms)
398
+ and marker scale-in (250ms, delayed 60ms). Existing IDs stay still when reordered
399
+ or edited. Reduced motion disables the entrance. Optional `relativeTimestamp`
400
+ (such as "just now") appears beside the absolute stamp only on the newest row,
401
+ in accent ink. The caller updates this text; Timeline does not run a clock.
390
402
 
391
403
  ```tsx
392
404
  <Timeline aria-label="Record history" events={[
@@ -400,11 +412,9 @@ Descriptions should say what changed and to where.
400
412
 
401
413
  Omit `undo` for read-only events. For a reversible batch, pass
402
414
  `undo: { label: 'Undo batch DEMO-12', onUndo }`. The app checks permissions,
403
- asks for confirmation when the batch affects finalized records, reverses the
404
- entire batch, and adds a new event. Timeline only calls `onUndo`; it does not
415
+ reverses the entire batch, and adds a new event without a confirmation dialog. Timeline only calls `onUndo`; it does not
405
416
  modify history.
406
- Import both the kit CSS and React CSS. The Privileged story shows confirmation
407
- and a reversing event.
417
+ Import both the kit CSS and React CSS. The Privileged story shows Undo adding a reversing event.
408
418
 
409
419
  ## Fields
410
420
 
@@ -68,6 +68,8 @@
68
68
  border-bottom: 1px solid var(--border);
69
69
  }
70
70
  .rx-modal__title {
71
+ margin: 0;
72
+ font-family: var(--font-sans);
71
73
  font-weight: var(--weight-normal);
72
74
  font-size: var(--text-base);
73
75
  color: var(--text);
@@ -189,8 +191,7 @@
189
191
  .ui-timeline__event:last-child::before {
190
192
  display: none;
191
193
  }
192
- .ui-timeline__event::after {
193
- content: "";
194
+ .ui-timeline__marker {
194
195
  position: absolute;
195
196
  inset-inline-start: 0;
196
197
  inset-block-start: calc(0.75em - var(--space-2) / 2);
@@ -214,6 +215,10 @@
214
215
  .ui-timeline__meta {
215
216
  font-size: var(--text-xs);
216
217
  }
218
+ .ui-timeline__relative {
219
+ color: var(--accent);
220
+ font-size: var(--text-xs);
221
+ }
217
222
  .ui-timeline__description {
218
223
  margin: var(--space-1) 0;
219
224
  }
@@ -224,6 +229,55 @@
224
229
  .ui-timeline__undo:focus-visible {
225
230
  color: var(--pink);
226
231
  }
232
+ .ui-timeline--kinds .ui-timeline__event {
233
+ padding-inline-start: var(--space-8);
234
+ }
235
+ .ui-timeline--kinds .ui-timeline__event::before {
236
+ inset-inline-start: calc(var(--space-5) / 2);
237
+ }
238
+ .ui-timeline--kinds .ui-timeline__marker {
239
+ inset-inline-start: calc((var(--space-5) - var(--space-2)) / 2);
240
+ background: var(--muted);
241
+ }
242
+ .ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker {
243
+ background: var(--accent);
244
+ }
245
+ .ui-timeline--kinds .ui-timeline__marker--kind {
246
+ display: flex;
247
+ align-items: center;
248
+ justify-content: center;
249
+ inset-inline-start: 0;
250
+ inset-block-start: calc(0.75em - var(--space-5) / 2);
251
+ width: var(--space-5);
252
+ height: var(--space-5);
253
+ border: 1px solid var(--muted);
254
+ background: var(--surface);
255
+ color: var(--muted);
256
+ }
257
+ .ui-timeline--kinds .ui-timeline__marker--reversal {
258
+ color: var(--pink);
259
+ }
260
+ .ui-timeline__marker--kind svg {
261
+ width: var(--space-3);
262
+ height: var(--space-3);
263
+ }
264
+ .ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker--kind {
265
+ border-color: var(--accent-strong);
266
+ background: var(--accent-strong);
267
+ color: var(--accent-contrast);
268
+ }
269
+ .ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker--reversal {
270
+ border-color: var(--pink);
271
+ background: var(--pink);
272
+ color: var(--danger-contrast);
273
+ }
274
+ .ui-timeline__event.is-entering {
275
+ --m-dist: var(--space-2);
276
+ animation: m-slide-up var(--dur-slow) var(--ease-out) both;
277
+ }
278
+ .ui-timeline__marker.is-entering {
279
+ animation: m-scale-in var(--dur-med) var(--ease-out) var(--delay-1) both;
280
+ }
227
281
 
228
282
  /* ../src/styles/input.css */
229
283
  .ui-field {
@@ -14,6 +14,8 @@ type ButtonProps = {
14
14
  block?: boolean;
15
15
  /** In flight: keeps focus, blocks activation, announces progress, and shows dots. */
16
16
  busy?: boolean;
17
+ /** Message after busy ends; an empty string leaves outcome announcements to the caller. */
18
+ completionMessage?: string;
17
19
  children?: ReactNode;
18
20
  } & ButtonHTMLAttributes<HTMLButtonElement>;
19
21
  declare const Button: react.ForwardRefExoticComponent<{
@@ -25,6 +27,8 @@ declare const Button: react.ForwardRefExoticComponent<{
25
27
  block?: boolean;
26
28
  /** In flight: keeps focus, blocks activation, announces progress, and shows dots. */
27
29
  busy?: boolean;
30
+ /** Message after busy ends; an empty string leaves outcome announcements to the caller. */
31
+ completionMessage?: string;
28
32
  children?: ReactNode;
29
33
  } & ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>;
30
34
 
@@ -498,11 +502,14 @@ type TabsProps = {
498
502
  };
499
503
  declare function Tabs({ items, value, onChange, label }: TabsProps): react.JSX.Element;
500
504
 
505
+ type TimelineEventKind = 'person' | 'rule' | 'reversal';
501
506
  type TimelineEvent = {
502
507
  id: string;
508
+ kind?: TimelineEventKind;
503
509
  actor: string;
504
510
  dateTime: string;
505
511
  timestamp: string;
512
+ relativeTimestamp?: string;
506
513
  description: string;
507
514
  meta?: ReactNode;
508
515
  undo?: {
@@ -608,4 +615,4 @@ declare function ThemeToggle({ labelled }: ThemeToggleProps): react.JSX.Element;
608
615
  declare const PAGE_SIZES: readonly number[];
609
616
  declare const DEFAULT_PAGE_SIZE: number;
610
617
 
611
- export { AppShell, type AppShellProps, type AppShellSection, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineProps, Toast, type ToastNotice, type ToastProps, sortTableRows, useToast };
618
+ export { AppShell, type AppShellProps, type AppShellSection, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineEventKind, type TimelineProps, Toast, type ToastNotice, type ToastProps, sortTableRows, useToast };
@@ -56,6 +56,7 @@ var Button = forwardRef(function Button2({
56
56
  iconOnly,
57
57
  block,
58
58
  busy,
59
+ completionMessage,
59
60
  children,
60
61
  type = "button",
61
62
  disabled,
@@ -83,7 +84,7 @@ var Button = forwardRef(function Button2({
83
84
  const prior = previous.current;
84
85
  if ((busy || prior?.busy) && (!prior || prior.text !== text2 || prior.busy !== busy)) {
85
86
  announcer.current ?? (announcer.current = acquireAnnouncer(buttonRef.current.ownerDocument));
86
- announcer.current.announce(`${text2}: ${busy ? "in progress" : "complete"}`);
87
+ announcer.current.announce(busy ? `${text2}: in progress` : completionMessage ?? `${text2}: complete`);
87
88
  }
88
89
  previous.current = { text: text2, busy };
89
90
  });
@@ -472,7 +473,7 @@ function Modal({ open, title, onClose, footer, children, initialFocusRef, role =
472
473
  ref: panel,
473
474
  children: [
474
475
  /* @__PURE__ */ jsxs5("div", { className: "rx-modal__head", children: [
475
- /* @__PURE__ */ jsx7("div", { className: "rx-modal__title", id: titleId, children: title }),
476
+ /* @__PURE__ */ jsx7("h2", { className: "rx-modal__title", id: titleId, children: title }),
476
477
  /* @__PURE__ */ jsx7(Button, { variant: "ghost", size: "sm", iconOnly: true, icon: "x", "aria-label": "Close", onClick: onClose })
477
478
  ] }),
478
479
  /* @__PURE__ */ jsx7("div", { className: "rx-modal__body", id: bodyId, ref: body, children }),
@@ -1890,25 +1891,62 @@ function Tabs({ items, value, onChange, label }) {
1890
1891
  }
1891
1892
 
1892
1893
  // src/Timeline.tsx
1894
+ import { useEffect as useEffect7, useRef as useRef12 } from "react";
1895
+ import { playEntrance as playEntrance2, prefersReducedMotion } from "@apliteni/apliteni-ui/motion";
1893
1896
  import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
1897
+ var kindIcons = { person: "user", rule: "bolt", reversal: "refresh" };
1894
1898
  function Timeline({ events, className, ...rest }) {
1895
- return /* @__PURE__ */ jsx22("ol", { ...rest, role: "list", className: ["ui-timeline", className].filter(Boolean).join(" "), children: events.map((event) => /* @__PURE__ */ jsxs18("li", { className: "ui-timeline__event", children: [
1896
- /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__head", children: [
1897
- /* @__PURE__ */ jsx22("span", { className: "ui-timeline__actor", children: event.actor }),
1898
- /* @__PURE__ */ jsx22("time", { dateTime: event.dateTime, children: event.timestamp })
1899
- ] }),
1900
- /* @__PURE__ */ jsx22("p", { className: "ui-timeline__description", children: event.description }),
1901
- (event.meta != null || event.undo) && /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__meta", children: [
1902
- event.meta != null && /* @__PURE__ */ jsx22("span", { children: event.meta }),
1903
- event.undo && /* @__PURE__ */ jsx22(Button, { variant: "ghost", size: "xs", className: "ui-btn ui-btn--ghost ui-btn--xs ui-timeline__undo", "aria-label": event.undo.label, onClick: event.undo.onUndo, children: "Undo" })
1904
- ] })
1905
- ] }, event.id)) });
1899
+ const previousIds = useRef12(new Set(events.map((event) => event.id)));
1900
+ const rows = useRef12(/* @__PURE__ */ new Map());
1901
+ useEffect7(() => {
1902
+ if (previousIds.current.size > 0 && !prefersReducedMotion()) {
1903
+ for (const event of events) {
1904
+ if (previousIds.current.has(event.id)) continue;
1905
+ const row = rows.current.get(event.id);
1906
+ if (!row) continue;
1907
+ playEntrance2(row);
1908
+ playEntrance2(row.querySelector(".ui-timeline__marker"));
1909
+ }
1910
+ }
1911
+ previousIds.current = new Set(events.map((event) => event.id));
1912
+ }, [events]);
1913
+ return /* @__PURE__ */ jsx22("ol", { ...rest, role: "list", className: ["ui-timeline", events.some((event) => event.kind) && "ui-timeline--kinds", className].filter(Boolean).join(" "), children: events.map((event, index) => /* @__PURE__ */ jsxs18(
1914
+ "li",
1915
+ {
1916
+ className: "ui-timeline__event",
1917
+ ref: (row) => {
1918
+ if (row) rows.current.set(event.id, row);
1919
+ else rows.current.delete(event.id);
1920
+ },
1921
+ children: [
1922
+ /* @__PURE__ */ jsx22("span", { "aria-hidden": "true", className: [
1923
+ "ui-timeline__marker",
1924
+ event.kind && "ui-timeline__marker--kind",
1925
+ event.kind === "reversal" && "ui-timeline__marker--reversal"
1926
+ ].filter(Boolean).join(" "), children: event.kind && /* @__PURE__ */ jsx22(Icon, { name: kindIcons[event.kind] }) }),
1927
+ /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__head", children: [
1928
+ /* @__PURE__ */ jsx22("span", { className: "ui-timeline__actor", children: event.actor }),
1929
+ /* @__PURE__ */ jsx22("time", { dateTime: event.dateTime, children: event.timestamp }),
1930
+ index === events.length - 1 && event.relativeTimestamp && /* @__PURE__ */ jsx22("span", { className: "ui-timeline__relative", children: event.relativeTimestamp })
1931
+ ] }),
1932
+ /* @__PURE__ */ jsx22("p", { className: "ui-timeline__description", children: event.description }),
1933
+ (event.meta != null || event.undo) && /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__meta", children: [
1934
+ event.meta != null && /* @__PURE__ */ jsx22("span", { children: event.meta }),
1935
+ event.undo && /* @__PURE__ */ jsx22(Button, { variant: "ghost", size: "xs", className: "ui-btn ui-btn--ghost ui-btn--xs ui-timeline__undo", "aria-label": event.undo.label, onClick: event.undo.onUndo, children: "Undo" })
1936
+ ] })
1937
+ ]
1938
+ },
1939
+ event.id
1940
+ )) });
1906
1941
  }
1907
1942
 
1908
1943
  // src/Field.tsx
1909
- import { useEffect as useEffect7, useId as useId8, useRef as useRef12, useState as useState7 } from "react";
1944
+ import { useEffect as useEffect8, useId as useId8, useRef as useRef13, useState as useState7 } from "react";
1910
1945
  import { esc, icon as icon4 } from "@apliteni/apliteni-ui";
1911
1946
  import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1947
+ function messages(id, hint, error) {
1948
+ return [error && `${id}-error`, hint && `${id}-hint`].filter(Boolean).join(" ") || void 0;
1949
+ }
1912
1950
  function Frame({ id, label, hint, error, required, children }) {
1913
1951
  return /* @__PURE__ */ jsxs19("div", { className: "ui-field", children: [
1914
1952
  /* @__PURE__ */ jsxs19("label", { className: "ui-field__label", htmlFor: id, children: [
@@ -1916,23 +1954,25 @@ function Frame({ id, label, hint, error, required, children }) {
1916
1954
  required && /* @__PURE__ */ jsx23("span", { className: "ui-field__req", "aria-hidden": "true", children: "*" })
1917
1955
  ] }),
1918
1956
  children,
1919
- (error || hint) && /* @__PURE__ */ jsx23(
1957
+ error && /* @__PURE__ */ jsx23(
1920
1958
  "div",
1921
1959
  {
1922
- id: `${id}-message`,
1923
- className: error ? "ui-field__error" : "ui-field__hint",
1924
- dangerouslySetInnerHTML: { __html: (error ? icon4("alert") : "") + esc(error || hint) }
1960
+ id: `${id}-error`,
1961
+ className: "ui-field__error",
1962
+ role: "alert",
1963
+ dangerouslySetInnerHTML: { __html: icon4("alert") + esc(error) }
1925
1964
  }
1926
- )
1965
+ ),
1966
+ hint && /* @__PURE__ */ jsx23("div", { id: `${id}-hint`, className: "ui-field__hint", children: hint })
1927
1967
  ] });
1928
1968
  }
1929
1969
  function wiring(id, hint, error) {
1930
- return { id, "aria-describedby": error || hint ? `${id}-message` : void 0, "aria-invalid": error ? true : void 0 };
1970
+ return { id, "aria-describedby": messages(id, hint, error), "aria-invalid": error ? true : void 0 };
1931
1971
  }
1932
1972
  function TextField({ label, hint, error, required, type = "text", unit, className = "", ...props }) {
1933
1973
  const id = useId8();
1934
1974
  const hasUnit = type === "number" && !!unit;
1935
- const describedBy = [error || hint ? `${id}-message` : "", hasUnit ? `${id}-unit` : ""].filter(Boolean).join(" ") || void 0;
1975
+ const describedBy = [messages(id, hint, error), hasUnit ? `${id}-unit` : ""].filter(Boolean).join(" ") || void 0;
1936
1976
  const control = /* @__PURE__ */ jsx23(
1937
1977
  "input",
1938
1978
  {
@@ -1978,10 +2018,10 @@ function SelectField({ label, hint, error, required, className = "", children, .
1978
2018
  }
1979
2019
  function FileField({ label, hint, error, required, disabled, onFileChange, className = "", ...props }) {
1980
2020
  const id = useId8();
1981
- const input = useRef12(null);
2021
+ const input = useRef13(null);
1982
2022
  const [file, setFile] = useState7(null);
1983
2023
  const [dragging, setDragging] = useState7(false);
1984
- useEffect7(() => {
2024
+ useEffect8(() => {
1985
2025
  const form = input.current?.form;
1986
2026
  const reset = () => {
1987
2027
  setFile(null);
@@ -2040,7 +2080,7 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
2040
2080
  }
2041
2081
 
2042
2082
  // src/Toast.tsx
2043
- import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect8, useRef as useRef13, useState as useState8 } from "react";
2083
+ import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect9, useLayoutEffect as useLayoutEffect4, useRef as useRef14, useState as useState8 } from "react";
2044
2084
  import { createPortal as createPortal4 } from "react-dom";
2045
2085
  import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
2046
2086
  var Context = createContext2(null);
@@ -2051,10 +2091,10 @@ function useToast() {
2051
2091
  return push;
2052
2092
  }
2053
2093
  function Notice({ notice, remove }) {
2054
- const root = useRef13(null);
2094
+ const root = useRef14(null);
2055
2095
  const [leaving2, setLeaving] = useState8(false);
2056
- const dismissed = useRef13(false);
2057
- const remaining = useRef13(5e3);
2096
+ const dismissed = useRef14(false);
2097
+ const remaining = useRef14(5e3);
2058
2098
  const [hovered, setHovered] = useState8(false);
2059
2099
  const [focused, setFocused] = useState8(false);
2060
2100
  const paused = hovered || focused;
@@ -2065,7 +2105,7 @@ function Notice({ notice, remove }) {
2065
2105
  if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) remove();
2066
2106
  else setLeaving(true);
2067
2107
  }, [remove]);
2068
- useEffect8(() => {
2108
+ useEffect9(() => {
2069
2109
  if (leaving2) {
2070
2110
  const element = root.current;
2071
2111
  const finish = (event) => {
@@ -2079,7 +2119,7 @@ function Notice({ notice, remove }) {
2079
2119
  };
2080
2120
  }
2081
2121
  }, [leaving2, remove]);
2082
- useEffect8(() => {
2122
+ useEffect9(() => {
2083
2123
  if (leaving2 || notice.action || paused) return;
2084
2124
  const started = Date.now();
2085
2125
  const timer = setTimeout(dismiss, remaining.current);
@@ -2135,25 +2175,47 @@ function Notice({ notice, remove }) {
2135
2175
  }
2136
2176
  );
2137
2177
  }
2178
+ var REACH = "--rx-toast-stack";
2179
+ function usePublishedReach(stack2, count) {
2180
+ useLayoutEffect4(() => {
2181
+ const element = stack2.current;
2182
+ const view = element?.ownerDocument.defaultView;
2183
+ const root = element?.ownerDocument.documentElement;
2184
+ if (!element || !view || !root) return;
2185
+ const publish = () => {
2186
+ const box = element.getBoundingClientRect();
2187
+ root.style.setProperty(REACH, `${box.height ? Math.round(view.innerHeight - box.top) : 0}px`);
2188
+ };
2189
+ publish();
2190
+ const observer = typeof view.ResizeObserver === "function" ? new view.ResizeObserver(publish) : void 0;
2191
+ observer?.observe(element);
2192
+ return () => {
2193
+ observer?.disconnect();
2194
+ root.style.removeProperty(REACH);
2195
+ };
2196
+ }, [stack2, count]);
2197
+ }
2138
2198
  function Toast({ children }) {
2139
2199
  const [entries, setEntries] = useState8([]);
2140
- const nextId = useRef13(0);
2200
+ const nextId = useRef14(0);
2201
+ const stack2 = useRef14(null);
2141
2202
  const push = useCallback3((notice) => {
2142
2203
  const id = nextId.current++;
2143
2204
  const remove = () => setEntries((current) => current.filter((entry) => entry.id !== id));
2144
2205
  setEntries((current) => [...current, { id, notice, remove }]);
2145
2206
  }, []);
2207
+ usePublishedReach(stack2, entries.length);
2146
2208
  return /* @__PURE__ */ jsxs20(Context.Provider, { value: push, children: [
2147
2209
  children,
2148
2210
  typeof document !== "undefined" && createPortal4(
2149
- /* @__PURE__ */ jsx24("div", { className: "ui-toast-stack rx-toast-stack", children: entries.map((entry) => /* @__PURE__ */ jsx24(Notice, { notice: entry.notice, remove: entry.remove }, entry.id)) }),
2211
+ /* @__PURE__ */ jsx24("div", { className: "ui-toast-stack rx-toast-stack", ref: stack2, children: entries.map((entry) => /* @__PURE__ */ jsx24(Notice, { notice: entry.notice, remove: entry.remove }, entry.id)) }),
2150
2212
  document.body
2151
2213
  )
2152
2214
  ] });
2153
2215
  }
2154
2216
 
2155
2217
  // src/AppShell.tsx
2156
- import { useEffect as useEffect9, useId as useId9, useState as useState9 } from "react";
2218
+ import { useEffect as useEffect10, useId as useId9, useState as useState9 } from "react";
2157
2219
  import { applyTheme, paletteHotkey, prism, railCollapsed, themeIcon, themeName } from "@apliteni/apliteni-ui";
2158
2220
  import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
2159
2221
  var path = (value) => value.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
@@ -2183,14 +2245,14 @@ function AppShell({
2183
2245
  const [theme, setTheme] = useState9("dark");
2184
2246
  const current = path(pathname);
2185
2247
  const active = sections.filter((section) => current === path(section.href) || path(section.href) !== "/" && current.startsWith(`${path(section.href)}/`)).sort((a, b) => path(b.href).length - path(a.href).length)[0];
2186
- useEffect9(() => {
2248
+ useEffect10(() => {
2187
2249
  setCollapsed(railCollapsed() ?? defaultCollapsed);
2188
2250
  }, [defaultCollapsed]);
2189
- useEffect9(() => {
2251
+ useEffect10(() => {
2190
2252
  setMore(false);
2191
2253
  setSearch(false);
2192
2254
  }, [pathname]);
2193
- useEffect9(() => {
2255
+ useEffect10(() => {
2194
2256
  const root = document.documentElement;
2195
2257
  const sync2 = () => setTheme(root.getAttribute("data-theme") === "light" ? "light" : "dark");
2196
2258
  sync2();
@@ -2198,7 +2260,7 @@ function AppShell({
2198
2260
  observer.observe(root, { attributes: true, attributeFilter: ["data-theme"] });
2199
2261
  return () => observer.disconnect();
2200
2262
  }, []);
2201
- useEffect9(() => {
2263
+ useEffect10(() => {
2202
2264
  const hotkey = (event) => {
2203
2265
  if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== "k") return;
2204
2266
  const target = event.target;