@sableui/react 0.9.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # What's new
2
2
 
3
+ ## 0.9.1
4
+
5
+ Fixes from a review of 0.9.0.
6
+
7
+ - **Stacked toasts stay on screen beside an open Sheet.** Moving a bottom stack
8
+ to the top left stacked toasts hanging from the top edge, off the screen.
9
+ They now re-anchor with the stack. A top Sheet also sends a top stack to the
10
+ bottom.
11
+ - **Toasts clear a Sheet of any width.** A side Sheet publishes its real width
12
+ (`--sable-sheet-right-w` / `--sable-sheet-left-w`) while open, so one widened
13
+ with `className` is cleared too.
14
+ - **Form no longer takes focus back after a slow submit** if the person has
15
+ moved to something outside the form. It also keeps looking for a few frames,
16
+ for errors set in a transition or an action that render late.
17
+ - **A loading Button refuses Enter and Space in its own `onKeyDown`** as well
18
+ as clicks, so a menu trigger rendered as a busy Button stays shut. Tab still
19
+ moves on.
20
+ - **Stepper's phone layout follows each step's status.** A flow driven by
21
+ `status`, or one that has finished, kept the wrong title or none.
22
+ - **`sable check` only warns about the whole-package `@source`.** A
23
+ single-module `@source` or an `@source not` exclusion no longer reads as one.
24
+ - **CommandMenu's clear button is out of the tab order** and doesn't take
25
+ focus from the field when pressed. Esc still clears from the field.
26
+ - **sableui.com** shows the 0.9 additions: plain Fieldset, RadioCard and Field
27
+ `trailing`, the MediaTile product tile, CommandMenu `leading`, async Button,
28
+ the Stepper on a phone, SkipLink, toasts beside a Sheet, and a note on each
29
+ reference layout. Build with Sable, Blocks and Page shells say they are
30
+ starting points.
31
+
3
32
  ## 0.9.0
4
33
 
5
34
  A storefront built on 0.8.2 (and the same brief on shadcn) found the seams
@@ -6,6 +6,7 @@ import {
6
6
  } from "react";
7
7
  import { useRender } from "@base-ui/react/use-render";
8
8
  import { cn } from "../../lib/cn.js";
9
+ import { isPromise } from "../../lib/is-promise.js";
9
10
  import { useTypeTier } from "../../lib/density.js";
10
11
  import { StackedActionsContext } from "../../lib/presentation.js";
11
12
  import { trimLabel } from "../../lib/trim-label.js";
@@ -36,6 +37,7 @@ function Button({
36
37
  disabled,
37
38
  children,
38
39
  onClick,
40
+ onKeyDown,
39
41
  ...props
40
42
  }) {
41
43
  const tier = useTypeTier();
@@ -43,13 +45,14 @@ function Button({
43
45
  const [pending, setPending] = useState(false);
44
46
  const pendingRef = useRef(false);
45
47
  const busy = loading || pending;
48
+ const refusing = () => busy || pendingRef.current || !isNativeButton && disabled === true;
46
49
  const handleClick = (event) => {
47
- if (busy || pendingRef.current || !isNativeButton && disabled) {
50
+ if (refusing()) {
48
51
  event.preventDefault();
49
52
  return;
50
53
  }
51
54
  const result = onClick?.(event);
52
- if (typeof result?.then === "function") {
55
+ if (isPromise(result)) {
53
56
  pendingRef.current = true;
54
57
  setPending(true);
55
58
  result.finally(() => {
@@ -58,6 +61,13 @@ function Button({
58
61
  });
59
62
  }
60
63
  };
64
+ const handleKeyDown = (event) => {
65
+ if ((event.key === "Enter" || event.key === " ") && refusing()) {
66
+ event.preventDefault();
67
+ return;
68
+ }
69
+ onKeyDown?.(event);
70
+ };
61
71
  const stacked = useContext(StackedActionsContext) && !String(size ?? "md").startsWith("icon");
62
72
  const resolvedSize = stacked ? "cta" : size ?? "md";
63
73
  const hasLabel = children != null && children !== false;
@@ -98,6 +108,7 @@ function Button({
98
108
  ),
99
109
  children: content,
100
110
  onClick: handleClick,
111
+ onKeyDown: handleKeyDown,
101
112
  ...resolvedBleed ? { "data-bleed": resolvedBleed } : null,
102
113
  ...busy ? { "aria-busy": true } : null,
103
114
  // A busy native button stays focusable: aria-disabled, with presses
@@ -194,6 +194,8 @@ function CommandMenuBody({
194
194
  variant: "ghost",
195
195
  size: "icon-xs",
196
196
  "aria-label": "Clear search",
197
+ tabIndex: -1,
198
+ onMouseDown: (event) => event.preventDefault(),
197
199
  className: "-my-1 text-muted-foreground hover:text-foreground",
198
200
  onClick: () => {
199
201
  setQuery("");
@@ -10,6 +10,7 @@ import {
10
10
  useRef
11
11
  } from "react";
12
12
  import { cn } from "../../lib/cn.js";
13
+ import { isPromise } from "../../lib/is-promise.js";
13
14
  import { Label } from "../label/index.js";
14
15
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
16
  function Form({
@@ -68,23 +69,27 @@ function Form({
68
69
  );
69
70
  }
70
71
  Form.displayName = "Form";
71
- var isPromise = (value) => typeof value?.then === "function";
72
+ var SETTLE_FRAMES = 10;
72
73
  function afterSubmit(form, result) {
73
- const focus = () => requestAnimationFrame(() => {
74
+ const attempt = (framesLeft) => requestAnimationFrame(() => {
74
75
  if (!form.isConnected) return;
75
76
  const active = document.activeElement;
77
+ if (active && active !== document.body && !form.contains(active)) return;
76
78
  if (active && form.contains(active) && active.getAttribute("aria-invalid") === "true") return;
77
79
  const first = [...form.querySelectorAll('[aria-invalid="true"]')].find(
78
80
  (el) => !(el instanceof HTMLInputElement && el.type === "hidden") && !el.closest("[aria-hidden='true'],[inert]")
79
81
  );
80
- if (!first) return;
82
+ if (!first) {
83
+ if (framesLeft > 0) attempt(framesLeft - 1);
84
+ return;
85
+ }
81
86
  first.focus();
82
87
  if (first instanceof HTMLInputElement && /^(?:text|search|email|tel|url|password|number)?$/.test(first.type)) {
83
88
  first.select();
84
89
  }
85
90
  });
86
- if (isPromise(result)) result.finally(focus);
87
- else focus();
91
+ if (isPromise(result)) result.finally(() => attempt(SETTLE_FRAMES));
92
+ else attempt(SETTLE_FRAMES);
88
93
  }
89
94
  function Field({
90
95
  label,
@@ -42,7 +42,21 @@ function SheetContent({
42
42
  reportSide?.(resolvedSide);
43
43
  }, [reportSide, resolvedSide]);
44
44
  const popupRef = useRef(null);
45
- const mergedRef = useMemo(() => mergeRefs(popupRef, ref), [ref]);
45
+ const [popup, setPopup] = useState(null);
46
+ const mergedRef = useMemo(() => mergeRefs(popupRef, ref, setPopup), [ref]);
47
+ useIsomorphicLayoutEffect(() => {
48
+ if (!popup || resolvedSide !== "left" && resolvedSide !== "right") return;
49
+ const root = document.documentElement;
50
+ const name = `--sable-sheet-${resolvedSide}-w`;
51
+ const publish = () => root.style.setProperty(name, `${popup.offsetWidth}px`);
52
+ publish();
53
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(publish);
54
+ observer?.observe(popup);
55
+ return () => {
56
+ observer?.disconnect();
57
+ root.style.removeProperty(name);
58
+ };
59
+ }, [popup, resolvedSide]);
46
60
  return /* @__PURE__ */ jsxs(SheetPortal, { children: [
47
61
  /* @__PURE__ */ jsx(Base.Backdrop, { className: "fixed inset-0 z-(--z-overlay) bg-overlay/(--scrim-alpha) opacity-[calc(1_-_var(--drawer-swipe-progress))] motion-fade data-[swiping]:transition-none" }),
48
62
  /* @__PURE__ */ jsx(Base.Viewport, { className: "fixed inset-0 z-(--z-overlay)", children: /* @__PURE__ */ jsx(
@@ -33,6 +33,10 @@ function Stepper({
33
33
  }) {
34
34
  const items = Children.toArray(children).filter(isValidElement);
35
35
  const labels = { ...DEFAULT_STATUS_LABELS, ...statusLabels };
36
+ const hasCurrent = items.some((child, index) => {
37
+ const own = child.props.status;
38
+ return (own ?? (index < current ? "complete" : index === current ? "current" : "upcoming")) === "current";
39
+ });
36
40
  return /* @__PURE__ */ jsx(
37
41
  "ol",
38
42
  {
@@ -55,7 +59,8 @@ function Stepper({
55
59
  current,
56
60
  orientation,
57
61
  indicator,
58
- statusLabels: labels
62
+ statusLabels: labels,
63
+ hasCurrent
59
64
  },
60
65
  children: child
61
66
  },
@@ -84,6 +89,7 @@ function StepperItem({
84
89
  const isLast = index === count - 1;
85
90
  const isCurrent = index === current;
86
91
  const statusLabel = (ctx?.statusLabels ?? DEFAULT_STATUS_LABELS)[status];
92
+ const hasCurrent = ctx?.hasCurrent ?? true;
87
93
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
88
94
  dot ? (
89
95
  // One title line tall (the step carries the title's type), so the dot
@@ -99,7 +105,10 @@ function StepperItem({
99
105
  // Narrow and horizontal: three two-word titles wrapped under their
100
106
  // connectors at 390px. The current step keeps its title; the rest
101
107
  // are discs on screen and full titles to a screen reader.
102
- orientation === "horizontal" && !isCurrent && "@max-md/stepper:sr-only"
108
+ // Keyed on the resolved status, not the index: a flow driven by each
109
+ // step's `status`, or finished (`current` past the end), still keeps
110
+ // the right title, or every title when no step is current.
111
+ orientation === "horizontal" && status !== "current" && hasCurrent && "@max-md/stepper:sr-only"
103
112
  ),
104
113
  children: [
105
114
  /* @__PURE__ */ jsxs(
@@ -121,6 +121,7 @@ function ToastRoot({ className, ...props }) {
121
121
  return /* @__PURE__ */ jsx(
122
122
  Base.Root,
123
123
  {
124
+ "data-sable-toast-stacked": stacked ? "" : void 0,
124
125
  className: cn(
125
126
  "flex w-full gap-3 rounded-md border border-border-floating bg-popover p-4 text-popover-foreground shadow-xl",
126
127
  stacked ? [STACKED_ROOT, STACKED_EDGE[edgeOf(position)]] : ENTER_MOTION[position],
@@ -128,7 +128,7 @@
128
128
  "dist/components/bubble/bubble.meta.js": "2202d4226cf79eb6",
129
129
  "dist/components/bubble/bubble.variants.js": "b1eefabd9f0bb425",
130
130
  "dist/components/bubble/index.js": "dfdc5326d5050d12",
131
- "dist/components/button/button.js": "85d996d9309e9e18",
131
+ "dist/components/button/button.js": "95d4cea99b77a48f",
132
132
  "dist/components/button/button.meta.js": "aa62fe875a12ac8a",
133
133
  "dist/components/button/button.variants.js": "d3054d6389644df0",
134
134
  "dist/components/button/index.js": "1ef5e4068111496a",
@@ -170,7 +170,7 @@
170
170
  "dist/components/combobox/combobox.meta.js": "38ef70bf96a3cf87",
171
171
  "dist/components/combobox/index.js": "f8f9863cf4f7e59b",
172
172
  "dist/components/command-menu/command-menu-trigger.js": "30b1606fb611c758",
173
- "dist/components/command-menu/command-menu.js": "6991ea75f72a0188",
173
+ "dist/components/command-menu/command-menu.js": "3478c25118653d88",
174
174
  "dist/components/command-menu/command-menu.meta.js": "dde6c79e36a55f25",
175
175
  "dist/components/command-menu/index.js": "c49dd188ea788ba5",
176
176
  "dist/components/context-card/context-card.js": "ab28de9684e32eea",
@@ -217,7 +217,7 @@
217
217
  "dist/components/font-picker/font-picker.js": "1d76108197a03759",
218
218
  "dist/components/font-picker/font-picker.meta.js": "846401404a0e03c5",
219
219
  "dist/components/font-picker/index.js": "eb8b97fcda8ff592",
220
- "dist/components/form/form.js": "3a724e6891a6a3df",
220
+ "dist/components/form/form.js": "af2fa2d3cb54d125",
221
221
  "dist/components/form/form.meta.js": "1e377418406a019d",
222
222
  "dist/components/form/index.js": "6c3047e437b10fba",
223
223
  "dist/components/gliding-highlight/gliding-highlight.js": "c424c85af1cc5665",
@@ -367,7 +367,7 @@
367
367
  "dist/components/settings-menu/settings-menu.meta.js": "46bdda712ce13d85",
368
368
  "dist/components/settings-menu/settings-menu.variants.js": "1eefc629793ed7b3",
369
369
  "dist/components/sheet/index.js": "b19aacba55e7de80",
370
- "dist/components/sheet/sheet.js": "851b9623a38e1e6c",
370
+ "dist/components/sheet/sheet.js": "61e0c11831dfa940",
371
371
  "dist/components/sheet/sheet.meta.js": "28cd51d5ba9872db",
372
372
  "dist/components/sheet/sheet.variants.js": "672d2fd7f746bd57",
373
373
  "dist/components/show-more/index.js": "e7c7e37ffedde2c2",
@@ -400,7 +400,7 @@
400
400
  "dist/components/status-dot/status-dot.meta.js": "90200e97feb25c97",
401
401
  "dist/components/status-dot/status-dot.variants.js": "d65605a0376e7d3b",
402
402
  "dist/components/stepper/index.js": "2346537b50c95017",
403
- "dist/components/stepper/stepper.js": "543b52e8d429fb3e",
403
+ "dist/components/stepper/stepper.js": "8e1bdfc654e5aa3e",
404
404
  "dist/components/stepper/stepper.meta.js": "6d17c7521e2aa31e",
405
405
  "dist/components/stepper/stepper.variants.js": "3c343c6e38e39d80",
406
406
  "dist/components/streaming-text/index.js": "c46856aa7c0718f5",
@@ -429,7 +429,7 @@
429
429
  "dist/components/thinking-orb/thinking-orb.meta.js": "dcc1982c0f9e9d01",
430
430
  "dist/components/thinking-orb/thinking-orb.render.js": "1d561ac1ffcd1e97",
431
431
  "dist/components/toast/index.js": "d13be0a6c335931d",
432
- "dist/components/toast/toast.js": "75052e65f5589fd2",
432
+ "dist/components/toast/toast.js": "f58c5e39eb3158a3",
433
433
  "dist/components/toast/toast.meta.js": "493dec13b784b6bc",
434
434
  "dist/components/toggle/index.js": "84a0a4bf596a1b7d",
435
435
  "dist/components/toggle/toggle.js": "90d0deb9c6af0d61",
@@ -471,8 +471,8 @@
471
471
  "dist/icon-sets/brands.js": "9a37d8739f14f2a9",
472
472
  "dist/icon-sets/reicon.d.ts": "52f6e41dc8137b4e",
473
473
  "dist/icon-sets/reicon.js": "89a930f17cc90cde",
474
- "dist/index.d.ts": "5bd49d44680db545",
475
- "dist/index.js": "aef044650352430a",
474
+ "dist/index.d.ts": "b3c6e9a4124867da",
475
+ "dist/index.js": "b7e51e26dc0b1e6c",
476
476
  "dist/ios/chrome.css": "a7ca9e3735d8658e",
477
477
  "dist/ios/chrome.js": "37091fef7b68bd28",
478
478
  "dist/ios/frame.css": "dd2703c944ce575a",
@@ -525,6 +525,7 @@
525
525
  "dist/lib/heading-row.js": "ef1faf4955e0fec6",
526
526
  "dist/lib/highlight-glide.js": "04a077a17dad7e52",
527
527
  "dist/lib/hit-target.js": "b2ad19bc6d3271e0",
528
+ "dist/lib/is-promise.js": "cc36f1bd7d4d4461",
528
529
  "dist/lib/meta.js": "e3b0c44298fc1c14",
529
530
  "dist/lib/portal-theme-scope.js": "2c0d22d961e51174",
530
531
  "dist/lib/presentation.js": "8e4f69cd7205bd55",
@@ -571,7 +572,7 @@
571
572
  "dist/presets/index.d.ts": "143528fa9cbec492",
572
573
  "dist/presets/index.js": "51babe07a2424380",
573
574
  "dist/registry-CyL28OzJ.d.ts": "b068fc89571517d8",
574
- "dist/styles.css": "05ff241061103966",
575
+ "dist/styles.css": "5a8e45c86522c82f",
575
576
  "dist/theme-provider-S0NVJs2Y.d.ts": "ae7728dddd10f563",
576
577
  "dist/themes/_contract.js": "cacc81c9295d55a2",
577
578
  "dist/themes/atelier/atelier.css": "e693fec4a6ae71a8",
package/dist/index.d.ts CHANGED
@@ -415,7 +415,7 @@ type ButtonBleed = "start" | "end" | "both" | "none";
415
415
  * Corner radius is theme-driven via `--button-radius` (falls back to full pill) —
416
416
  * see `button.variants.ts`.
417
417
  */
418
- declare function Button({ variant, size, className, render, ref, type, loading, loadingLabel, iconStart, iconEnd, badge, bleed, disabled, children, onClick, ...props }: ButtonProps): react.ReactElement<unknown, string | react.JSXElementConstructor<any>>;
418
+ declare function Button({ variant, size, className, render, ref, type, loading, loadingLabel, iconStart, iconEnd, badge, bleed, disabled, children, onClick, onKeyDown, ...props }: ButtonProps): react.ReactElement<unknown, string | react.JSXElementConstructor<any>>;
419
419
  declare namespace Button {
420
420
  var displayName: string;
421
421
  }
@@ -8378,6 +8378,6 @@ interface UseCopyResult {
8378
8378
  */
8379
8379
  declare function useCopy(text: string, { timeout }?: UseCopyOptions): UseCopyResult;
8380
8380
 
8381
- declare const SABLE_UI_VERSION: "0.9.0";
8381
+ declare const SABLE_UI_VERSION: "0.9.1";
8382
8382
 
8383
8383
  export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, AppHeader, type AppHeaderProps, AppShell, type AppShellProps, AreaChart, type AreaChartProps, type ArtTone, Attachment, type AttachmentProps, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, type AvatarGroupCountProps, type AvatarGroupProps, AvatarImage, type AvatarProps, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, type BadgeAppearance, type BadgeProps, type BadgeTone, Banner, type BannerProps, BarChart, type BarChartProps, BottomNav, BottomNavItem, type BottomNavItemProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, BreadcrumbPage, type BreadcrumbPageProps, BreadcrumbSeparator, type BreadcrumbSeparatorProps, Breadcrumbs, type BreadcrumbsProps, Browser, type BrowserProps, Bubble, type BubbleProps, BubbleReaction, type BubbleReactionProps, BubbleReactions, type BubbleReactionsProps, Button, type ButtonBleed, type ButtonProps, CHART_SERIES_COUNT, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, type CalendarProps, Card, CardAction, CardContent, type CardContentProps, CardDescription, CardFooter, type CardFooterProps, CardHeader, CardList, type CardProps, CardTitle, type CardTitleProps, Carousel, type CarouselProps, type CarouselSlidesToShow, type ChartCategories, type ChartCenterLabel, type ChartCurve, type ChartDatum, type ChartSeries, type ChartTone, type ChartTooltipIndicator, type ChartTooltipOptions, type ChartValueFormatter, ChatComposer, type ChatComposerProps, Checkbox, CheckboxCard, type CheckboxCardProps, type CheckboxCardTone, CheckboxGroup, type CheckboxGroupProps, Code, CodeBlock, type CodeBlockProps, CodeHighlightProvider, type CodeHighlighter, type CodeProps, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, type ColorFieldProps, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, type ComboboxItemGroup, ComboboxItemIndicator, ComboboxList, ComboboxPortal, type ComboboxProps, ComboboxValue, ComboboxVirtualList, type ComboboxVirtualListProps, type CommandFilter, type CommandItem, CommandMenu, CommandMenuGroup, type CommandMenuGroupProps, CommandMenuItem, type CommandMenuItemProps, CommandMenuPanel, type CommandMenuPanelProps, type CommandMenuProps, CommandMenuTrigger, type CommandMenuTriggerProps, type ComponentGroup, type ComponentGuidance, ComponentMeta, type ComponentRegister, type ConstraintFlag, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DataTable, type DataTableColumn, type DataTableProps, type DataTableRowKey, DatePicker, type DatePickerProps, DateTile, type DateTileProps, Density, DensityProvider, Density as DensityValue, DestructiveActionModal, type DestructiveActionModalProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, type DialogSize, DialogTitle, DialogTrigger, DisclosurePicker, DisclosurePickerEmpty, DisclosurePickerList, type DisclosurePickerListProps, DisclosurePickerOption, type DisclosurePickerOptionProps, DisclosurePickerPanel, type DisclosurePickerPanelProps, type DisclosurePickerProps, type DisclosurePickerSearch, DisclosurePickerTrigger, type DisclosurePickerTriggerProps, EmptyState, type EmptyStateProps, Entity, type EntityProps, Feedback, type FeedbackData, type FeedbackProps, type FeedbackRating, Field, type FieldProps, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, type FieldsetVariant, FileDropzone, type FileDropzoneProps, type FileRejection, type FileRejectionCode, FontPicker, type FontPickerFont, type FontPickerOption, type FontPickerProps, Form, type FormProps, GlidingHighlight, type GlidingHighlightProps, GradientArt, type GradientArtProps, type Hsv, InlineStatus, type InlineStatusProps, type StatusTone as InlineTone, Input, type InputProps$1 as InputProps, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, Label, type LabelProps$2 as LabelProps, LineChart, type LineChartProps, Marker, type MarkerProps, Marquee, type MarqueeProps, MediaImage, type MediaImageProps, MediaTile, type MediaTileProps, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, type MessageProps, MessageScroller, type MessageScrollerHandle, type MessageScrollerProps, Meter, MeterIndicator, MeterLabel, type MeterSize, type MeterTone, MeterTrack, MeterValue, MiddleTruncate, type MiddleTruncateProps, type MotionPolicy, MotionProvider, type MotionProviderProps, Note, type NoteAction, type NoteProps, NotificationBadge, type NotificationBadgeProps, NumberField, type NumberFieldProps, type OrbMode, type OrbSize, OtpField, type OtpFieldProps, OverflowMenu, type OverflowMenuProps, PageHeader, type PageHeaderProps, PageLoader, type PageLoaderProps, Pagination, type PaginationProps, Panel, PanelGroup, PanelResizer, type PanelResizerProps, PanelSection, type PanelWidthBounds, Phone, type PhoneProps, PieChart, type PieChartProps, Pill, type PillProps, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, Presence, type PresenceProps, type Presentation, PresentationProvider, Progress, ProgressIndicator, ProgressLabel, type ProgressSize, ProgressTrack, ProgressValue, PropertyPair, PropertyRow, RadarChart, type RadarChartProps, RadialChart, type RadialChartProps, Radio, RadioCard, type RadioCardProps, type RadioCardTone, RadioGroup, RelativeTime, type RelativeTimeProps, Reveal, type RevealProps, type Rgb, SABLE_UI_VERSION, SEGMENTED_BAR_TONES, STATUS_TONES, STATUS_TONE_ICON, type ScrollCommandOptions, Scroller, type ScrollerProps, ScrubField, type ScrubFieldProps, SearchInput, type SearchInputProps, SectionHeader, type SectionHeaderProps, SegmentedBar, type SegmentedBarProps, type SegmentedBarSegment, type SegmentedBarTone, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps$3 as SeparatorProps, SettingsMenu, type SettingsMenuProps, SettingsRow, type SettingsRowProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetList, SheetPortal, SheetTitle, SheetTrigger, ShowMore, type ShowMoreProps, SideNav, type SideNavActiveVariant, SideNavGroup, type SideNavGroupProps, SideNavItem, type SideNavItemProps, type SideNavProps, type SideNavSize, Sidebar, SidebarBrand, type SidebarBrandProps, SidebarFooter, SidebarHeader, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarProps, SimpleTooltip, Skeleton, SkipLink, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, Sparkline, type SparklineProps, Spinner, type SpinnerProps, StatusBar, StatusBarGroup, StatusBarSeparator, StatusDot, type StatusDotProps, type StatusTone, type StepStatus, Stepper, StepperItem, type StepperItemProps, type StepperOrientation, type StepperProps, StreamingText, type StreamingTextProps, Switch, TRANSPARENT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, TextReveal, type TextRevealProps, Textarea, type TextareaProps, ThemeId, ThemeManifest, ThemeMode$1 as ThemeManifestMode, type ThemeMode, ThemePreview, type ThemePreviewProps, ThemeSelector, type ThemeSelectorProps, ThemeSwitcher, type ThemeSwitcherProps, ThinkingOrb, type ThinkingOrbProps, ToastAction, type ToastApi, ToastClose, ToastContent, ToastDescription, type ToastManagerApi, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, ToastRoot, ToastTitle, type ToastType, ToastViewport, Toggle, ToggleGroup, type ToggleGroupAppearance, Toggle as ToggleGroupItem, Toolbar, ToolbarButton, type ToolbarButtonProps, ToolbarGroup, type ToolbarGroupProps, ToolbarInput, type ToolbarInputProps, ToolbarLink, type ToolbarLinkProps, type ToolbarProps, ToolbarSeparator, type ToolbarSeparatorProps, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, type UseCopyOptions, type UseCopyResult, type UseInViewOptions, type UsePanelWidthOptions, Video, type VideoProps, ViewTransitionName, type ViewTransitionNameProps, Workspace, WorkspaceBar, WorkspaceCanvas, accordionMeta, appHeaderMeta, appShellMeta, areaChartMeta, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bannerMeta, bannerVariants, barChartMeta, bottomNavMeta, breadcrumbsMeta, browserMeta, bubbleMeta, buttonMeta, buttonVariants, calendarMeta, cardMeta, carouselMeta, chartColor, toneColor as chartToneColor, chatComposerMeta, checkboxCardMeta, checkboxMeta, cn, codeBlockMeta, codeMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, commandMenuMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, dataTableMeta, datePickerMeta, dateTileMeta, destructiveActionModalMeta, dialogMeta, disclosurePickerMeta, emptyStateMeta, entityMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, fontPickerMeta, formMeta, formatFileSize, formatHex, glidingHighlightMeta, gradientArtMeta, hsvToRgb, hueColor, inlineStatusMeta, inputMeta, isTransparent, kbdMeta, labelMeta, lineChartMeta, markerMeta, marqueeMeta, mediaImageMeta, mediaTileMeta, menuMeta, messageMeta, messageScrollerMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, middleTruncateMeta, noteMeta, noteVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, numberFieldMeta, otpFieldMeta, overflowMenuMeta, pageHeaderMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, phoneMeta, pieChartMeta, pillMeta, pillVariants, popoverMeta, progressMeta, progressTrackVariants, radarChartMeta, radialChartMeta, radioCardMeta, radioMeta, relativeTimeMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, searchInputMeta, sectionHeaderMeta, segmentedBarMeta, segmentedBarSegmentVariants, segmentedBarVariants, selectMeta, separatorMeta, settingsMenuMeta, sheetMeta, sheetVariants, showMoreMeta, sideNavMeta, sidebarMeta, skeletonMeta, sliderMeta, sparklineMeta, spinnerMeta, startViewTransition, statusBarMeta, statusDotMeta, statusDotVariants, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, streamingTextMeta, supportsViewTransitions, switchMeta, tableMeta, tabsMeta, textareaMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, toggleMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, tooltipMeta, treeIndent, treeMeta, useAnchoredDensity, useComboboxFilteredItems, useCommandMenuShortcut, useCopy, useDensity, useDisclosurePicker, useInView, useMotion, usePanelWidth, usePresentation, useReducedMotion, useScrollActivity, useSidebar, useToast, useToastManager, useTypeTier, videoMeta, workspaceMeta };
package/dist/index.js CHANGED
@@ -160,7 +160,7 @@ import { PresentationProvider, usePresentation } from "./lib/presentation.js";
160
160
  import { useScrollActivity } from "./lib/use-scroll-activity.js";
161
161
  import { useCopy } from "./lib/use-copy.js";
162
162
  import { STATUS_TONES, STATUS_TONE_ICON } from "./lib/status-tone.js";
163
- var SABLE_UI_VERSION = "0.9.0";
163
+ var SABLE_UI_VERSION = "0.9.1";
164
164
  export {
165
165
  ART_TOKENS,
166
166
  CHART_SERIES_COUNT,
@@ -0,0 +1,5 @@
1
+ // src/lib/is-promise.ts
2
+ var isPromise = (value) => typeof value?.then === "function";
3
+ export {
4
+ isPromise
5
+ };
package/dist/styles.css CHANGED
@@ -1549,15 +1549,23 @@
1549
1549
 
1550
1550
  /* Toasts keep clear of an open Sheet's actions. A right sheet's pinned footer
1551
1551
  (Check out) sat under a bottom-right toast, and its close button under a
1552
- top-right one. Where there is room, the stack moves beside the sheet (both are
1553
- w-95); on a narrower screen a bottom stack moves to the top, away from the
1554
- footer. A bottom sheet sends a bottom stack to the top at any width. */
1552
+ top-right one.
1553
+ - Where there is room, a stack on the sheet's side moves beside it. The sheet
1554
+ publishes its real width (--sable-sheet-right-w / -left-w) while mounted.
1555
+ - On a narrower screen the sheet leaves no room beside it. A bottom stack
1556
+ moves to the top, off the footer's actions; a top stack stays, over the
1557
+ header.
1558
+ - A bottom sheet sends a bottom stack to the top, a top sheet sends a top
1559
+ stack to the bottom, at any width.
1560
+ A stack that moves to the other edge re-anchors its stacked toasts too: they
1561
+ hang from the viewport's edge and grow away from it, so a bottom-anchored
1562
+ deck at the top would grow off the screen. */
1555
1563
  @media (width >= 52rem) {
1556
1564
  :root:has([data-sable-sheet="right"][data-open]) [data-sable-toast-viewport][data-position$="right"] {
1557
- right: calc(min(var(--spacing) * 95, 90vw) + var(--spacing) * 4);
1565
+ right: calc(var(--sable-sheet-right-w, min(var(--spacing) * 95, 90vw)) + var(--spacing) * 4);
1558
1566
  }
1559
1567
  :root:has([data-sable-sheet="left"][data-open]) [data-sable-toast-viewport][data-position$="left"] {
1560
- left: calc(min(var(--spacing) * 95, 90vw) + var(--spacing) * 4);
1568
+ left: calc(var(--sable-sheet-left-w, min(var(--spacing) * 95, 90vw)) + var(--spacing) * 4);
1561
1569
  }
1562
1570
  }
1563
1571
  @media (width < 52rem) {
@@ -1566,10 +1574,45 @@
1566
1574
  top: calc(var(--spacing) * 4);
1567
1575
  bottom: auto;
1568
1576
  }
1577
+ :root:has([data-sable-sheet="right"][data-open]) [data-sable-toast-viewport][data-position="bottom-right"] [data-sable-toast-stacked],
1578
+ :root:has([data-sable-sheet="left"][data-open]) [data-sable-toast-viewport][data-position="bottom-left"] [data-sable-toast-stacked] {
1579
+ top: 0;
1580
+ bottom: auto;
1581
+ transform-origin: top;
1582
+ --toast-dir: 1;
1583
+ &::after {
1584
+ top: 100%;
1585
+ bottom: auto;
1586
+ }
1587
+ }
1569
1588
  }
1570
1589
  :root:has([data-sable-sheet="bottom"][data-open]) [data-sable-toast-viewport][data-position^="bottom"] {
1571
1590
  top: calc(var(--spacing) * 4);
1572
1591
  bottom: auto;
1592
+ & [data-sable-toast-stacked] {
1593
+ top: 0;
1594
+ bottom: auto;
1595
+ transform-origin: top;
1596
+ --toast-dir: 1;
1597
+ &::after {
1598
+ top: 100%;
1599
+ bottom: auto;
1600
+ }
1601
+ }
1602
+ }
1603
+ :root:has([data-sable-sheet="top"][data-open]) [data-sable-toast-viewport][data-position^="top"] {
1604
+ top: auto;
1605
+ bottom: calc(var(--spacing) * 4);
1606
+ & [data-sable-toast-stacked] {
1607
+ top: auto;
1608
+ bottom: 0;
1609
+ transform-origin: bottom;
1610
+ --toast-dir: -1;
1611
+ &::after {
1612
+ top: auto;
1613
+ bottom: 100%;
1614
+ }
1615
+ }
1573
1616
  }
1574
1617
 
1575
1618
  /* `Col span={{ base, md, lg }}` / `start={…}` — each breakpoint resolves to the
@@ -43,6 +43,10 @@ for each recognizable element with `sable search "<need>" --json`; inspect the
43
43
  matching `component <Name> --props` and `--examples` before implementation.
44
44
  Use Sable's composed anatomy and supported variants. A hand-built button, field,
45
45
  menu, dialog, table, or card that imitates a shipped component violates the contract.
46
+ That binds behaviour and parts, not layouts. Results marked ◇ (patterns, blocks,
47
+ and compositions such as MediaTile) are references: use one when its layout fits
48
+ the brief, otherwise compose your own from Sable's parts. The brief's signature
49
+ surfaces, where the product's identity lives, are designed from its direction.
46
50
 
47
51
  Resolve styling through component variants, documented utilities/recipes, then
48
52
  semantic tokens. App-owned CSS handles uncovered content and composition; it
package/llms-full.txt CHANGED
@@ -1040,6 +1040,10 @@ for each recognizable element with `sable search "<need>" --json`; inspect the
1040
1040
  matching `component <Name> --props` and `--examples` before implementation.
1041
1041
  Use Sable's composed anatomy and supported variants. A hand-built button, field,
1042
1042
  menu, dialog, table, or card that imitates a shipped component violates the contract.
1043
+ That binds behaviour and parts, not layouts. Results marked ◇ (patterns, blocks,
1044
+ and compositions such as MediaTile) are references: use one when its layout fits
1045
+ the brief, otherwise compose your own from Sable's parts. The brief's signature
1046
+ surfaces, where the product's identity lives, are designed from its direction.
1043
1047
 
1044
1048
  Resolve styling through component variants, documented utilities/recipes, then
1045
1049
  semantic tokens. App-owned CSS handles uncovered content and composition; it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sableui/react",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Sable: themeable React components, layout patterns, and tools for coding agents.",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/registry.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://sable.design/registry.schema.json",
3
3
  "name": "@sableui/react",
4
- "version": "0.9.0",
4
+ "version": "0.9.1",
5
5
  "description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
6
6
  "engine": {
7
7
  "behavior": "@base-ui/react",
@@ -195,7 +195,10 @@ export function sourcesCss(pkgRoot, root, outFile) {
195
195
  * that component unstyled, silently — so it fails. A whole-package @source is
196
196
  * correct, just heavy, so it only gets a note.
197
197
  */
198
- const WHOLE_DIST = /@source[^;]*(?:@sableui\/react|packages\/sable)\/dist(?:\/\*\*)?[^;]*;/
198
+ // The whole-package scan only: `dist`, `dist/` or a `dist/**` glob. A single
199
+ // module (`dist/components/chart/chart.js`, a lazy route's) and an exclusion
200
+ // (`@source not …`) are deliberate and trim nothing away.
201
+ const WHOLE_DIST = /@source\s+(?!not\b)["'][^"']*(?:@sableui\/react|packages\/sable)\/dist\/?(?:\*\*(?:\/[^"']*)?)?["']/
199
202
 
200
203
  export function sourcesCheck(pkgRoot, root, cssFiles) {
201
204
  const generated = cssFiles.find((f) => f.text.includes(SOURCES_MARKER))