rift-ds 1.16.0 → 1.18.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.
Files changed (40) hide show
  1. package/bin/templates/blocks/app/dashboard/page.module.css +4 -3
  2. package/bin/templates/blocks/app/dashboard/page.tsx +1 -1
  3. package/bin/templates/blocks/components/blocks/AppShell.module.css +50 -0
  4. package/bin/templates/blocks/components/blocks/AppShell.tsx +26 -7
  5. package/chat/ChatLauncher.d.ts +11 -1
  6. package/chat/ChatLauncher.js +7 -1
  7. package/chat/ChatThreads.d.ts +3 -1
  8. package/chat/ChatThreads.js +3 -1
  9. package/chat/ChatWidget.css +21 -0
  10. package/components/AppLayout/AppLayout.css +90 -2
  11. package/components/AppLayout/AppLayout.d.ts +10 -2
  12. package/components/AppLayout/AppLayout.js +20 -2
  13. package/components/AppSidebar/AppSidebar.css +104 -38
  14. package/components/AppSidebar/AppSidebar.d.ts +15 -4
  15. package/components/AppSidebar/AppSidebar.js +8 -2
  16. package/components/DataTable/DataTable.js +29 -9
  17. package/components/DataTable/FilterEditor.js +1 -2
  18. package/components/DataTable/filterModel.js +4 -3
  19. package/components/DataTable/views.d.ts +9 -0
  20. package/components/DataTable/views.js +10 -1
  21. package/components/SegmentedControl/SegmentedControl.css +69 -0
  22. package/components/SegmentedControl/SegmentedControl.d.ts +4 -3
  23. package/components/SegmentedControl/SegmentedControl.js +90 -48
  24. package/components/Stat/Stat.css +57 -0
  25. package/components/Stat/Stat.d.ts +8 -2
  26. package/components/Stat/Stat.js +4 -2
  27. package/components/TableToolbar/TableToolbar.css +6 -0
  28. package/components/TableToolbar/TableToolbar.js +52 -16
  29. package/components/ThemeToggle/ThemeToggle.css +2 -72
  30. package/components/ThemeToggle/ThemeToggle.d.ts +1 -1
  31. package/components/ThemeToggle/ThemeToggle.js +4 -40
  32. package/components/registry.json +1 -1
  33. package/components/registry.json.d.ts +1 -1
  34. package/components/registry.json.js +1 -1
  35. package/package.json +1 -1
  36. package/tokens/registry.json +2 -0
  37. package/tokens/registry.json.d.ts +2 -0
  38. package/tokens/registry.json.js +1 -1
  39. package/tokens/tokens-dark.css +7 -0
  40. package/tokens/tokens-light.css +7 -0
@@ -51,6 +51,22 @@
51
51
  backdrop-filter: blur(24px);
52
52
  }
53
53
 
54
+ /* The inset layout's gap: between the content surface and the viewport
55
+ edges, and around a docked chat panel's inset surface (ChatLauncher's
56
+ surface="inset" falls back to the same value when this sheet is absent). */
57
+ :root {
58
+ --ds-shell-inset: 10px;
59
+ }
60
+
61
+ /* ============================================
62
+ INSET VARIANT — the rail flush on the app
63
+ shell colour, beside a content surface inset
64
+ from the edges (AppLayout's inset layout)
65
+ ============================================ */
66
+ .ds-app-sidebar--inset {
67
+ background-color: var(--color-bg-app-shell);
68
+ }
69
+
54
70
  /* ============================================
55
71
  TOP SECTION — logo + nav
56
72
  ============================================ */
@@ -605,6 +621,15 @@
605
621
  inset-inline-start: calc(var(--ds-sidebar-float-inset, 20px) + 280px + var(--gap-300));
606
622
  }
607
623
 
624
+ /* Inset: the side panel joins the rail on the shell plane, one hairline
625
+ divider between the two levels, and the inset content surface starts
626
+ beyond both (AppLayout reserves the panel's width while it shows). The
627
+ trailing edge needs no border: the surface's own edge ends the panel. */
628
+ .ds-app-sidebar__panel--inset {
629
+ background-color: var(--color-bg-app-shell);
630
+ border-inline-end-color: transparent;
631
+ }
632
+
608
633
  /* ============================================
609
634
  PANEL MODE RAIL — labels under icons
610
635
  With the second level in the panel, the
@@ -612,85 +637,126 @@
612
637
  widens to 80px and names every destination:
613
638
  a pill indicator behind the icon, the label
614
639
  beneath (the navigation-rail pattern).
640
+
641
+ Expand/collapse holds one seat, as ThreadPanel
642
+ does: the logo, every icon and the avatar sit
643
+ at the same x and y in both states, so only the
644
+ rail's width, the rows' explicit heights and
645
+ the labels' opacity move. The caption under the
646
+ icon fades out as the inline label fades in.
615
647
  ============================================ */
616
648
  .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) {
617
649
  width: 80px;
618
650
  }
619
651
 
620
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__logo {
621
- /* Centres the 24px mark in the 80px rail: (80 - 24) / 2. */
622
- padding: 0 calc(var(--padding-500) + var(--padding-200));
652
+ /* The seat: 12px row margin + 16px row padding puts every 24px icon's
653
+ centre on the 80px rail's centre line (28 + 12 = 40), and the logo,
654
+ the category labels and the profile row share that leading edge. */
655
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__logo {
656
+ padding-inline-start: calc(var(--padding-500) + var(--padding-200));
657
+ }
658
+
659
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__category {
660
+ padding-inline-start: calc(var(--padding-500) + var(--padding-200));
623
661
  }
624
662
 
625
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__nav {
663
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__nav {
626
664
  gap: 0;
665
+ transition: gap var(--motion-duration-slow) var(--motion-ease-emphasized);
627
666
  }
628
667
 
629
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn {
630
- height: auto;
631
- margin: 0 var(--padding-100);
632
- /* Room between the content and the focus ring; the rows' spacing lives
633
- here rather than in the nav gap, so the rhythm is unchanged. */
634
- padding: var(--padding-100) 0;
635
- justify-content: center;
636
- border-radius: var(--radius-300);
668
+ .ds-app-sidebar--panel-mode.ds-app-sidebar--expanded .ds-app-sidebar__nav {
669
+ gap: var(--gap-100);
670
+ }
671
+
672
+ /* The icon is pinned to the row's top in both states, so it holds its
673
+ y while the row's height glides between its two explicit values. */
674
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn {
675
+ position: relative;
676
+ isolation: isolate;
677
+ align-items: flex-start;
678
+ /* Collapsed: the 32px indicator, the caption beneath it, a 4px beat
679
+ above, between and below. */
680
+ height: calc(32px + var(--font-caption-line-height) + var(--padding-100) * 3);
681
+ margin: 0 var(--padding-300);
682
+ padding: var(--padding-200) var(--padding-200) var(--padding-200) var(--padding-400);
637
683
  overflow: visible;
684
+ transition:
685
+ background-color var(--motion-duration-fast) var(--motion-ease-standard),
686
+ height var(--motion-duration-slow) var(--motion-ease-emphasized);
638
687
  }
639
688
 
640
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover,
641
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active {
642
- background-color: transparent;
689
+ .ds-app-sidebar--panel-mode.ds-app-sidebar--expanded .ds-app-sidebar__btn {
690
+ height: 40px;
643
691
  }
644
692
 
645
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-left {
646
- flex-direction: column;
647
- gap: var(--gap-100);
648
- width: 100%;
693
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn-left {
694
+ height: 24px;
649
695
  }
650
696
 
651
- /* The trailing chevron and count pill are faded out on any collapsed
652
- rail, but faded still takes width: on this centred column it pushed
653
- the icon and label off centre. Here they leave the layout outright. */
654
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-chevron,
655
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-badge {
656
- display: none;
697
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn-chevron {
698
+ flex-shrink: 0;
657
699
  }
658
700
 
659
701
  /* The indicator: a 56 x 32 pill behind the icon carries hover and the
660
- selection, so the label stays legible on the page ground. */
661
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-icon {
662
- display: flex;
663
- align-items: center;
664
- justify-content: center;
702
+ selection while collapsed, so the caption stays legible on the page
703
+ ground; expanded, the row's own pill takes over and the two crossfade. */
704
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__btn::before {
705
+ content: '';
706
+ position: absolute;
707
+ top: var(--padding-100);
708
+ inset-inline-start: 0;
709
+ z-index: -1;
665
710
  width: 56px;
666
711
  height: 32px;
667
712
  border-radius: var(--radius-pill);
713
+ background-color: transparent;
668
714
  transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
669
715
  }
670
716
 
671
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover .ds-app-sidebar__btn-icon,
672
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active .ds-app-sidebar__btn-icon {
717
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover,
718
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active {
719
+ background-color: transparent;
720
+ }
721
+
722
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn:hover::before,
723
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active::before {
673
724
  background-color: var(--color-action-passive-bg-hover);
674
725
  }
675
726
 
676
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn-label {
677
- opacity: 1;
678
- max-width: 100%;
727
+ /* The caption: centred under the indicator, the rail's 72px wide
728
+ (80 less a 4px inset either side), fading out as the rail opens. */
729
+ .ds-app-sidebar__btn-caption {
730
+ position: absolute;
731
+ top: calc(32px + var(--padding-100) * 2);
732
+ inset-inline-start: calc(var(--padding-200) * -1);
733
+ width: 72px;
679
734
  font-family: var(--font-caption-family);
680
735
  font-size: var(--font-caption-size);
681
736
  font-weight: var(--font-caption-weight);
682
737
  line-height: var(--font-caption-line-height);
683
738
  letter-spacing: var(--font-caption-letter-spacing);
739
+ color: var(--color-text-primary);
684
740
  text-align: center;
741
+ white-space: nowrap;
742
+ overflow: hidden;
685
743
  text-overflow: ellipsis;
744
+ pointer-events: none;
745
+ opacity: 1;
746
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
747
+ }
748
+
749
+ .ds-app-sidebar--expanded .ds-app-sidebar__btn-caption {
750
+ opacity: 0;
751
+ transition-duration: var(--motion-duration-fast);
686
752
  }
687
753
 
688
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active .ds-app-sidebar__btn-label {
754
+ .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__btn--active .ds-app-sidebar__btn-caption {
689
755
  font-weight: var(--font-paragraph-sm-emphasis-weight);
690
756
  }
691
757
 
692
- .ds-app-sidebar--panel-mode:not(.ds-app-sidebar--expanded) .ds-app-sidebar__profile {
693
- /* Centres the 40px avatar in the 80px rail. */
758
+ .ds-app-sidebar--panel-mode .ds-app-sidebar__profile {
759
+ /* Centres the 40px avatar in the 80px rail, and holds it there open. */
694
760
  padding: 0 var(--padding-500);
695
761
  }
696
762
 
@@ -61,6 +61,8 @@ export interface AppSidebarProfile {
61
61
  /** Avatar image URL */
62
62
  avatarUrl?: string;
63
63
  }
64
+ /** How the rail meets the page: edge to edge, floating as a card, or flush beside an inset content surface. */
65
+ export type AppSidebarLayout = 'flush' | 'floating' | 'inset';
64
66
  /** Props owned by AppSidebar itself — everything else falls through to the `<nav>`. */
65
67
  type AppSidebarOwnProps = {
66
68
  /** Navigation sections */
@@ -97,10 +99,19 @@ type AppSidebarOwnProps = {
97
99
  /** Callback when profile more button clicked */
98
100
  onProfileMore?: () => void;
99
101
  /**
100
- * Floats the rail off the viewport edges as a glass card: inset with
101
- * rounded corners, the translucent glass fill over a backdrop blur, and
102
- * the floating shadow. The inset defaults to 20px and is overridable via
103
- * the --ds-sidebar-float-inset custom property.
102
+ * How the rail meets the page. `flush` runs it edge to edge on the page
103
+ * colour. `floating` floats it off the viewport edges as a glass card:
104
+ * inset with rounded corners, the translucent glass fill over a backdrop
105
+ * blur, and the floating shadow (the inset defaults to 20px and is
106
+ * overridable via the --ds-sidebar-float-inset custom property). `inset`
107
+ * runs it flush on the app shell colour, beside a content surface inset
108
+ * from the edges, as AppLayout's `inset` layout draws it. Defaults to
109
+ * `flush` (or `floating` while the deprecated `floating` is set).
110
+ */
111
+ layout?: AppSidebarLayout;
112
+ /**
113
+ * Floats the rail as a glass card, the same as `layout="floating"`.
114
+ * @deprecated Use `layout="floating"`.
104
115
  */
105
116
  floating?: boolean;
106
117
  /** Rendered under the logo row, above the nav; fades out while collapsed */
@@ -46,7 +46,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
46
46
  expanded: controlledExpanded,
47
47
  onExpandedChange,
48
48
  onProfileMore,
49
- floating = false,
49
+ layout: layoutProp,
50
+ floating: floatingProp = false,
50
51
  topSlot,
51
52
  footerSlot,
52
53
  className = "",
@@ -63,6 +64,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
63
64
  ...rest
64
65
  }, ref) {
65
66
  const baseClass = "ds-app-sidebar";
67
+ const layout = layoutProp ?? (floatingProp ? "floating" : "flush");
68
+ const floating = layout === "floating";
66
69
  const Link = useLinkComponent(linkComponent);
67
70
  const avatarSeed = (item) => typeof item.pixelAvatar === "string" ? item.pixelAvatar : item.label;
68
71
  const avatarItems = sections.flatMap(
@@ -228,6 +231,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
228
231
  // a modal overlay would be all cost and no labels.
229
232
  isExpanded || mobileOpen ? `${baseClass}--expanded` : "",
230
233
  floating ? `${baseClass}--floating` : "",
234
+ layout === "inset" ? `${baseClass}--inset` : "",
231
235
  mobileOpen ? `${baseClass}--mobile-open` : "",
232
236
  isPanelMode ? `${baseClass}--panel-mode` : "",
233
237
  className
@@ -320,6 +324,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
320
324
  ),
321
325
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-label`, children: item.label })
322
326
  ] }),
327
+ isPanelMode && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-caption`, "aria-hidden": "true", children: item.label }),
323
328
  item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge`, children: item.badge }),
324
329
  hasChildren && /* @__PURE__ */ jsx(
325
330
  "span",
@@ -480,7 +485,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
480
485
  panelVisible ? `${baseClass}__panel--visible` : "",
481
486
  isExpanded ? `${baseClass}__panel--beside-expanded` : "",
482
487
  isPanelMode ? `${baseClass}__panel--panel-mode` : "",
483
- floating ? `${baseClass}__panel--floating` : ""
488
+ floating ? `${baseClass}__panel--floating` : "",
489
+ layout === "inset" ? `${baseClass}__panel--inset` : ""
484
490
  ].filter(Boolean).join(" "),
485
491
  items: panelSource.items,
486
492
  title: panelSource.label,
@@ -22,7 +22,7 @@ import { TableContext } from "./tableContext.js";
22
22
  import { Popover } from "../Popover/Popover.js";
23
23
  import { typedComparator, TYPE_DEFAULTS, typedText, formatNumber } from "./columnTypes.js";
24
24
  import { TypedCell } from "./TypedCell.js";
25
- import { reconcileView } from "./views.js";
25
+ import { slotStartingView, reconcileView } from "./views.js";
26
26
  import { calculate, TOTALS, NUMERIC_TOTALS } from "./aggregates.js";
27
27
  import { useLinkComponent } from "../../behaviors/LinkProvider.js";
28
28
  import { TYPED_KINDS, LIVE_KINDS, EDITOR_MIN_WIDTH } from "./editorKinds.js";
@@ -272,11 +272,21 @@ function DataTable({
272
272
  const locale = useLocale(localeProp);
273
273
  const collator = useMemo(() => textCollator(locale), [locale]);
274
274
  const compareValues = useMemo(() => valueComparator(collator), [collator]);
275
- const [size, setSize] = useControllable(sizeProp, defaultSize ?? "default", onSizeChange);
276
- const [search, setSearchState] = useControllable(searchProp, defaultSearch ?? "", onSearchChange);
275
+ const [startingView] = useState(() => slotStartingView(toolbar));
276
+ const startsWith = (key, controlled) => controlled === void 0 && startingView?.[key] !== void 0;
277
+ const [size, setSize] = useControllable(
278
+ sizeProp,
279
+ (startsWith("size", sizeProp) ? startingView.size : void 0) ?? defaultSize ?? "default",
280
+ onSizeChange
281
+ );
282
+ const [search, setSearchState] = useControllable(
283
+ searchProp,
284
+ (startsWith("search", searchProp) ? startingView.search : void 0) ?? defaultSearch ?? "",
285
+ onSearchChange
286
+ );
277
287
  const [filters, setFiltersState] = useControllable(
278
288
  filtersProp,
279
- defaultFilters ?? [],
289
+ (startsWith("filters", filtersProp) ? startingView.filters : void 0) ?? defaultFilters ?? [],
280
290
  onFiltersChange
281
291
  );
282
292
  const filterLabels = {
@@ -286,7 +296,7 @@ function DataTable({
286
296
  };
287
297
  const [pageSize, setPageSize] = useControllable(
288
298
  pageSizeProp,
289
- defaultPageSize,
299
+ (startsWith("pageSize", pageSizeProp) ? startingView.pageSize : void 0) ?? defaultPageSize,
290
300
  onPageSizeChange
291
301
  );
292
302
  const [page, setPageState] = useControllable(pageProp, defaultPage ?? 1, onPageChange);
@@ -321,14 +331,15 @@ function DataTable({
321
331
  return () => clearTimeout(timer);
322
332
  }, [announceRequest]);
323
333
  const isSortControlled = sortsProp !== void 0 || sort !== void 0;
334
+ const openingSorts = defaultSorts ?? (defaultSort ? [defaultSort] : []);
324
335
  const [uncontrolledSorts, setUncontrolledSorts] = useState(
325
- defaultSorts ?? (defaultSort ? [defaultSort] : [])
336
+ (startsWith("sorts", sortsProp ?? sort) ? startingView.sorts : void 0) ?? openingSorts
326
337
  );
327
338
  const currentSorts = sortsProp ?? (sort !== void 0 ? sort ? [sort] : [] : uncontrolledSorts);
328
339
  const sortOf = (key) => currentSorts.find((entry) => entry.key === key);
329
340
  const [groupingState, setGroupingState] = useControllable(
330
341
  groupingProp,
331
- defaultGrouping ?? [],
342
+ (startsWith("grouping", groupingProp) ? startingView.grouping : void 0) ?? defaultGrouping ?? [],
332
343
  onGroupingChange
333
344
  );
334
345
  const [expandedState, setExpanded] = useControllable(
@@ -391,7 +402,7 @@ function DataTable({
391
402
  const canHide = (col) => !col.locked && (col.hideable ?? true);
392
403
  const [widthsState, setWidths] = useControllable(
393
404
  columnWidths,
394
- defaultColumnWidths ?? {},
405
+ (startsWith("columnWidths", columnWidths) ? startingView.columnWidths : void 0) ?? defaultColumnWidths ?? {},
395
406
  onColumnWidthsChange
396
407
  );
397
408
  const [orderState, setOrder] = useControllable(columnOrder, defaultColumnOrder ?? [], onColumnOrderChange);
@@ -1022,7 +1033,16 @@ function DataTable({
1022
1033
  size,
1023
1034
  ...pageSize !== void 0 ? { pageSize } : {}
1024
1035
  };
1025
- const [baseView] = useState(currentView);
1036
+ const [baseView] = useState(() => ({
1037
+ ...currentView,
1038
+ ...startsWith("sorts", sortsProp ?? sort) ? { sorts: openingSorts } : {},
1039
+ ...startsWith("filters", filtersProp) ? { filters: defaultFilters ?? [] } : {},
1040
+ ...startsWith("search", searchProp) ? { search: defaultSearch ?? "" } : {},
1041
+ ...startsWith("columnWidths", columnWidths) ? { columnWidths: defaultColumnWidths ?? {} } : {},
1042
+ ...startsWith("grouping", groupingProp) ? { grouping: defaultGrouping ?? [] } : {},
1043
+ ...startsWith("size", sizeProp) ? { size: defaultSize ?? "default" } : {},
1044
+ ...startsWith("pageSize", pageSizeProp) ? { pageSize: defaultPageSize } : {}
1045
+ }));
1026
1046
  const viewColumns = columns.map((col) => ({
1027
1047
  key: col.key,
1028
1048
  filterKind: col.type ? TYPE_DEFAULTS[col.type].filter : void 0,
@@ -120,9 +120,8 @@ function FilterEditor({
120
120
  }
121
121
  );
122
122
  })();
123
- const isList = column.kind === "enum" || column.kind === "tags";
124
123
  return /* @__PURE__ */ jsxs("div", { className: "ds-data-table__filter-editor", role: "group", "aria-label": labels.editor(column.label), children: [
125
- !isList && /* @__PURE__ */ jsx(
124
+ operators.length > 1 && /* @__PURE__ */ jsx(
126
125
  Dropdown,
127
126
  {
128
127
  size,
@@ -2,9 +2,10 @@ const OPERATORS = {
2
2
  text: ["contains", "notContains", "is", "isNot", "startsWith", "endsWith", "isEmpty", "isNotEmpty"],
3
3
  number: ["eq", "neq", "lt", "lte", "gt", "gte", "between", "isEmpty", "isNotEmpty"],
4
4
  date: ["is", "before", "after", "between", "isEmpty", "isNotEmpty"],
5
- // A list filter is its checkboxes, so it has the one condition they mean.
6
- enum: ["isAnyOf"],
7
- tags: ["hasAnyOf"],
5
+ // A list filter is its checkboxes; the condition says what a tick means,
6
+ // and the first, "any of", is the one a new filter starts with.
7
+ enum: ["isAnyOf", "isNoneOf"],
8
+ tags: ["hasAnyOf", "hasAllOf", "hasNoneOf"],
8
9
  boolean: ["isChecked", "isNotChecked"]
9
10
  };
10
11
  const VALUELESS = /* @__PURE__ */ new Set(["isEmpty", "isNotEmpty", "isChecked", "isNotChecked"]);
@@ -30,6 +30,15 @@ export interface TableView {
30
30
  /** Rows per page. */
31
31
  pageSize?: number;
32
32
  }
33
+ /**
34
+ * The view a table starts on when a TableToolbar with saved views sits
35
+ * directly in its `toolbar` slot: the active one (`activeView`, else
36
+ * `defaultActiveView`, else the first). Read from the element's props, so
37
+ * the table's first render, the server's HTML included, is already that
38
+ * view; the toolbar applies it on mount too, for a bar the table cannot
39
+ * see into (one wrapped in another component).
40
+ */
41
+ export declare function slotStartingView(toolbar: unknown): TableView | undefined;
33
42
  /** What reconciling needs to know about a column. */
34
43
  export interface ViewColumn {
35
44
  key: string;
@@ -1,4 +1,12 @@
1
1
  import { OPERATORS, VALUELESS } from "./filterModel.js";
2
+ function slotStartingView(toolbar) {
3
+ if (!toolbar || typeof toolbar !== "object" || !("props" in toolbar)) return void 0;
4
+ const props = toolbar.props;
5
+ const views = props?.views ?? props?.defaultViews;
6
+ if (!Array.isArray(views) || views.length === 0) return void 0;
7
+ const id = props?.activeView ?? props?.defaultActiveView;
8
+ return (views.find((v) => v.id === id) ?? views[0]).view;
9
+ }
2
10
  function reconcileView(view, columns) {
3
11
  const byKey = new Map(columns.map((col) => [col.key, col]));
4
12
  const known = (key) => byKey.has(key);
@@ -35,5 +43,6 @@ function reconcileView(view, columns) {
35
43
  return next;
36
44
  }
37
45
  export {
38
- reconcileView
46
+ reconcileView,
47
+ slotStartingView
39
48
  };
@@ -228,3 +228,72 @@
228
228
  .ds-segmented-control--compact .ds-segmented-control__icon {
229
229
  --icon-size: var(--icon-size-500);
230
230
  }
231
+
232
+ /* ============================================
233
+ ICON STRIP (showLabels={false})
234
+ The glyphs alone, with each label turned
235
+ into the segment's tooltip: it appears below
236
+ the segment on hover and keyboard focus, on
237
+ Tooltip's surface, so an unfamiliar glyph is
238
+ never left to be guessed. The component
239
+ places the label with the shared
240
+ anchored-position behaviour, so a seat that
241
+ clips cannot cut it off, and it flips above
242
+ the strip when there is no room below. The
243
+ label stays in the accessibility tree as the
244
+ segment's name. A strip the collapse ladder
245
+ squeezed to icons keeps its labels clipped
246
+ instead.
247
+ ============================================ */
248
+
249
+ .ds-segmented-control__label--tip {
250
+ /* Out of flow at rest; the behaviour makes it fixed once it shows. */
251
+ position: absolute;
252
+ z-index: var(--layer-floating);
253
+ padding: var(--padding-150) var(--padding-200);
254
+ border-radius: var(--radius-100);
255
+ border: var(--border-025) solid var(--color-bg-container-border);
256
+ background-color: var(--color-bg-page-primary);
257
+ color: var(--color-text-primary);
258
+ box-shadow: var(--shadow-floating);
259
+ font-family: var(--font-paragraph-sm-family);
260
+ font-size: var(--font-paragraph-sm-size);
261
+ font-weight: var(--font-paragraph-sm-weight);
262
+ line-height: var(--font-paragraph-sm-line-height);
263
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
264
+ white-space: nowrap;
265
+ pointer-events: none;
266
+ opacity: 0;
267
+ transform: translateY(calc(-1 * var(--gap-100)));
268
+ transition:
269
+ opacity var(--motion-duration-fast) var(--motion-ease-standard),
270
+ transform var(--motion-duration-fast) var(--motion-ease-standard);
271
+ }
272
+
273
+ /* --motion-duration-slow matches MOTION_HOVER_SHOW_DELAY_MS, the hover-intent
274
+ delay Tooltip uses in JS, so the label does not flash while the pointer
275
+ passes through. Hiding takes no delay, like a tooltip leaving. */
276
+ .ds-segmented-control__segment:hover .ds-segmented-control__label--tip,
277
+ .ds-segmented-control__segment:focus-visible .ds-segmented-control__label--tip {
278
+ opacity: 1;
279
+ transform: none;
280
+ transition-delay: var(--motion-duration-slow);
281
+ }
282
+
283
+ /* Flipped above the strip, it slides down into place instead. */
284
+ .ds-segmented-control__label--tip[data-side='top'] {
285
+ transform: translateY(var(--gap-100));
286
+ }
287
+
288
+ /* Without an inline label a segment's asymmetric padding reads lopsided:
289
+ square each one into an even pill around its glyph. */
290
+ .ds-segmented-control--icons .ds-segmented-control__segment {
291
+ padding-inline: var(--padding-200);
292
+ }
293
+
294
+ /* ds-allow(motion): drops the label tooltip's slide outright rather than playing it at 0.01ms. */
295
+ @media (prefers-reduced-motion: reduce) {
296
+ .ds-segmented-control__label--tip {
297
+ transition: none;
298
+ }
299
+ }
@@ -65,9 +65,10 @@ type SegmentedControlOwnProps = {
65
65
  collapse?: boolean;
66
66
  /**
67
67
  * Show each segment's label beside its icon. Set it to `false` for an
68
- * icon-only strip: the labels stay in the accessibility tree as the
69
- * segments' names, and a segment without an icon keeps its label on
70
- * screen. Defaults to `true`.
68
+ * icon-only strip: each label becomes its segment's tooltip on hover and
69
+ * keyboard focus and stays the segment's name in the accessibility tree,
70
+ * and a segment without an icon keeps its label on screen. Defaults to
71
+ * `true`.
71
72
  */
72
73
  showLabels?: boolean;
73
74
  /**