@galaxy-io/dls 2.8.1 → 2.10.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 (106) hide show
  1. package/README.md +2 -2
  2. package/dist/beacons/Beacon.d.ts +1 -1
  3. package/dist/beacons/Beacon.js +12 -28
  4. package/dist/brand/GalaxyAlchemyWordmark.d.ts +4 -1
  5. package/dist/brand/GalaxyAlchemyWordmark.js +7 -1
  6. package/dist/brand/GalaxyFilamentWordmark.d.ts +5 -2
  7. package/dist/brand/GalaxyFilamentWordmark.js +9 -2
  8. package/dist/brand/GalaxyLogomark.js +3 -0
  9. package/dist/brand/PoweredBy.d.ts +27 -0
  10. package/dist/brand/PoweredBy.js +96 -0
  11. package/dist/brand/internal/svg/GalaxyAlchemyWordmarkSvg.d.ts +1 -1
  12. package/dist/brand/internal/svg/GalaxyAlchemyWordmarkSvg.js +34 -8
  13. package/dist/brand/internal/svg/GalaxyFilamentWordmarkSvg.d.ts +1 -1
  14. package/dist/brand/internal/svg/GalaxyFilamentWordmarkSvg.js +2 -2
  15. package/dist/charts/Heatmap.js +25 -9
  16. package/dist/charts/Sparkline.d.ts +1 -1
  17. package/dist/charts/Sparkline.js +10 -26
  18. package/dist/charts/chartFormat.d.ts +1 -1
  19. package/dist/charts/chartFormat.js +2 -2
  20. package/dist/charts/heatmapGeometry.d.ts +14 -0
  21. package/dist/charts/heatmapGeometry.js +17 -1
  22. package/dist/chips/Chip.d.ts +2 -2
  23. package/dist/chips/Chip.js +15 -31
  24. package/dist/drawer/Drawer.d.ts +2 -2
  25. package/dist/drawer/Drawer.js +1 -1
  26. package/dist/editor/CodeEditor.d.ts +1 -1
  27. package/dist/editor/CodeEditor.js +2 -1
  28. package/dist/feedback/Alert.d.ts +36 -9
  29. package/dist/feedback/Alert.js +26 -8
  30. package/dist/feedback/ProgressBar.d.ts +4 -1
  31. package/dist/feedback/ProgressBar.js +17 -30
  32. package/dist/feedback/ProgressCircle.d.ts +1 -1
  33. package/dist/feedback/ProgressCircle.js +14 -30
  34. package/dist/hooks/useEndReached.d.ts +40 -0
  35. package/dist/hooks/useEndReached.js +69 -0
  36. package/dist/icons/Icon.d.ts +36 -8
  37. package/dist/icons/Icon.js +14 -7
  38. package/dist/inputs/CronInput.js +114 -78
  39. package/dist/inputs/DateInput.d.ts +1 -1
  40. package/dist/inputs/DateInput.js +10 -10
  41. package/dist/inputs/MultiSelectInput.d.ts +3 -1
  42. package/dist/inputs/MultiSelectInput.js +8 -3
  43. package/dist/inputs/SelectInput.d.ts +8 -1
  44. package/dist/inputs/SelectInput.js +52 -25
  45. package/dist/inputs/internal/selectAll.d.ts +11 -0
  46. package/dist/inputs/internal/selectAll.js +28 -0
  47. package/dist/json/JsonViewer.js +7 -6
  48. package/dist/layout/AppFrame.d.ts +20 -0
  49. package/dist/layout/AppFrame.js +71 -0
  50. package/dist/layout/EmptyLayout.d.ts +53 -0
  51. package/dist/layout/EmptyLayout.js +59 -0
  52. package/dist/layout/ErrorLayout.d.ts +42 -0
  53. package/dist/layout/ErrorLayout.js +60 -0
  54. package/dist/layout/PageLayout.d.ts +26 -0
  55. package/dist/layout/PageLayout.js +116 -0
  56. package/dist/layout/PendingLayout.d.ts +30 -0
  57. package/dist/layout/PendingLayout.js +57 -0
  58. package/dist/layout/internal/errorState.d.ts +14 -0
  59. package/dist/layout/internal/errorState.js +49 -0
  60. package/dist/layout/internal/region.d.ts +10 -0
  61. package/dist/layout/internal/region.js +24 -0
  62. package/dist/lists/DescriptionList.js +1 -0
  63. package/dist/menu/Menu.d.ts +2 -2
  64. package/dist/menu/Menu.js +6 -13
  65. package/dist/modal/Modal.d.ts +2 -2
  66. package/dist/modal/Modal.js +1 -1
  67. package/dist/navigation/CommandPalette.js +4 -11
  68. package/dist/navigation/SidebarNav.d.ts +5 -0
  69. package/dist/navigation/SidebarNav.js +41 -22
  70. package/dist/navigation/Topbar.js +4 -0
  71. package/dist/shapes/Circle.d.ts +1 -1
  72. package/dist/shapes/Circle.js +10 -26
  73. package/dist/shapes/Square.d.ts +1 -1
  74. package/dist/shapes/Square.js +10 -26
  75. package/dist/styles/internal/roleColor.d.ts +3 -0
  76. package/dist/styles/internal/roleColor.js +42 -0
  77. package/dist/styles/mixins.js +1 -1
  78. package/dist/styles.css +1 -1
  79. package/dist/table/InfiniteSpreadsheet.js +6 -6
  80. package/dist/table/InfiniteTable.d.ts +57 -18
  81. package/dist/table/InfiniteTable.js +5 -3
  82. package/dist/table/TableCore.d.ts +11 -2
  83. package/dist/table/TableCore.js +108 -87
  84. package/dist/table/VirtualizedInfiniteTable.d.ts +58 -19
  85. package/dist/table/VirtualizedInfiniteTable.js +9 -6
  86. package/dist/table/useInfiniteScrollSentinel.d.ts +13 -13
  87. package/dist/table/useInfiniteScrollSentinel.js +7 -51
  88. package/dist/table/useTableColumnLayout.d.ts +8 -2
  89. package/dist/table/useTableColumnLayout.js +36 -13
  90. package/dist/theme/tokens/dark.js +1 -1
  91. package/dist/theme/tokens/light.js +4 -3
  92. package/dist/theme/tokens/types.d.ts +7 -7
  93. package/dist/timeline/Timeline.d.ts +1 -1
  94. package/dist/timeline/Timeline.js +6 -24
  95. package/dist/tokens.css +1 -1
  96. package/dist/utils/format.d.ts +35 -10
  97. package/dist/utils/format.js +60 -12
  98. package/dist/utils/search.d.ts +15 -0
  99. package/dist/utils/search.js +24 -0
  100. package/dist/vite/index.d.ts +16 -2
  101. package/dist/vite/index.js +28 -19
  102. package/dist/vite/internal/themeBoot.d.ts +25 -0
  103. package/dist/vite/internal/themeBoot.js +44 -0
  104. package/dist/vite/internal/wywOptions.d.ts +46 -0
  105. package/dist/vite/internal/wywOptions.js +44 -0
  106. package/package.json +34 -1
@@ -22,13 +22,14 @@ import { rootStyle, useOwnField } from "./internal/ownField.js";
22
22
  import { BareFieldContext, useIsBareField } from "./internal/bareField.js";
23
23
  import "./internal/innerButton.js";
24
24
  import { useDedupedOpen, useRestoreFocusAfterOutsidePress } from "./internal/useComboboxPopover.js";
25
+ import { isSearchMatch, normalizeSearchTerm } from "../utils/search.js";
25
26
  /* empty css */
26
27
  import { styled } from "@linaria/react";
27
28
  import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
28
29
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
29
30
  import { cx } from "@linaria/core";
30
31
  import { useMergeRefs } from "@floating-ui/react";
31
- import { CaretDownIcon, CheckIcon, PlusIcon, XIcon } from "@phosphor-icons/react";
32
+ import { CaretDownIcon, CheckIcon, MinusIcon, PlusIcon, XIcon } from "@phosphor-icons/react";
32
33
  import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
33
34
  //#region src/inputs/SelectInput.tsx
34
35
  /**
@@ -63,7 +64,7 @@ var SelectInputSize = /* @__PURE__ */ function(SelectInputSize) {
63
64
  SelectInputSize["X_LARGE"] = "X_LARGE";
64
65
  return SelectInputSize;
65
66
  }({});
66
- /** Resting fill of the field (Constitution V-A surface set; 1.x `InputVariant` unchanged). */
67
+ /** Resting fill of the field (AGENTS.md "Variant": surface set; 1.x `InputVariant` unchanged). */
67
68
  var SelectInputVariant = /* @__PURE__ */ function(SelectInputVariant) {
68
69
  /** `background.base` fill. */
69
70
  SelectInputVariant["BASE"] = "BASE";
@@ -227,7 +228,7 @@ var SELECT_INPUT_VARIANT_TO_CSS_MAP = {
227
228
  var GHOST_CSS = "gx-SelectInput-GHOST_CSS-g1yocujd";
228
229
  /**
229
230
  * Hover on an editable field: `border.hovered` (stronger than `border.primary` in both themes,
230
- * Constitution III.6).
231
+ * `tokens.test.ts` holds the order).
231
232
  */
232
233
  var SELECT_INPUT_VARIANT_TO_HOVER_CSS_MAP = {
233
234
  ["BASE"]: "gx-SelectInput-BASE-b1uexxly",
@@ -451,22 +452,14 @@ var SELECT_INPUT_SIZE_TO_GROUP_LABEL_CSS_MAP = {
451
452
  };
452
453
  /** Space above and below the pinned section's separator (Menu's). */
453
454
  var SEPARATOR_CSS = "gx-SelectInput-SEPARATOR_CSS-s9yly5q";
454
- /** Lower-cased, trimmed term (the filter's comparison form; Menu's rule). */
455
- function normalizeTerm(term) {
456
- return term.trim().toLocaleLowerCase();
457
- }
458
455
  /** An option matches when the term is empty or its label (or description) contains it. */
459
- function matches(term, option) {
460
- if (term === "") return true;
461
- if (option.label.toLocaleLowerCase().includes(term)) return true;
462
- return option.description?.toLocaleLowerCase().includes(term) ?? false;
463
- }
456
+ var matches = (term, option) => isSearchMatch(term, option.label, option.description);
464
457
  /** A printable character that should drive typeahead or the search term. */
465
458
  function isPrintableKey(event) {
466
459
  return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
467
460
  }
468
- /** Builds the rows in order: pinned, separator, sections, Create, then the status row. */
469
- function buildModel({ options, pinnedIds, term, isFiltering, selected, limitReason, createLabel, isLoading, listboxId }) {
461
+ /** Builds the rows in order: select-all, pinned, separator, sections, Create, then the status row. */
462
+ function buildModel({ options, pinnedIds, term, isFiltering, selected, limitReason, createLabel, selectAllLabel, isLoading, listboxId }) {
470
463
  const indexById = /* @__PURE__ */ new Map();
471
464
  options.forEach((option, index) => {
472
465
  if (!indexById.has(option.id)) indexById.set(option.id, index);
@@ -525,7 +518,19 @@ function buildModel({ options, pinnedIds, term, isFiltering, selected, limitReas
525
518
  section.rows.push(makeItem(option, index));
526
519
  });
527
520
  const restCount = sections.reduce((count, section) => count + section.rows.length, 0);
528
- const hasSeparator = pinned.length > 0 && restCount > 0;
521
+ const targets = [...pinned, ...sections.flatMap((section) => section.rows)].filter((item) => !item.isDisabled);
522
+ const selectedTargetCount = targets.filter((item) => item.isSelected).length;
523
+ const selectAll = selectAllLabel === null || targets.length === 0 ? null : {
524
+ kind: "selectAll",
525
+ key: "select-all",
526
+ domId: `${listboxId}-select-all`,
527
+ navIndex: -1,
528
+ flatIndex: -1,
529
+ label: selectAllLabel,
530
+ targetIds: targets.map((item) => item.option.id),
531
+ state: selectedTargetCount === targets.length ? "checked" : selectedTargetCount > 0 ? "mixed" : "unchecked"
532
+ };
533
+ const hasSeparator = (pinned.length > 0 || selectAll !== null) && restCount > 0;
529
534
  const create = createLabel === null ? null : {
530
535
  kind: "create",
531
536
  key: "create",
@@ -542,6 +547,7 @@ function buildModel({ options, pinnedIds, term, isFiltering, selected, limitReas
542
547
  nav.push(item);
543
548
  flat.push(item);
544
549
  };
550
+ if (selectAll !== null) pushNav(selectAll);
545
551
  for (const item of pinned) pushNav(item);
546
552
  if (hasSeparator) flat.push({
547
553
  kind: "separator",
@@ -558,6 +564,7 @@ function buildModel({ options, pinnedIds, term, isFiltering, selected, limitReas
558
564
  key: status
559
565
  });
560
566
  return {
567
+ selectAll,
561
568
  pinned,
562
569
  hasSeparator,
563
570
  sections,
@@ -568,9 +575,12 @@ function buildModel({ options, pinnedIds, term, isFiltering, selected, limitReas
568
575
  optionCount: position
569
576
  };
570
577
  }
571
- /** The first enabled row, else the first row (or -1). */
578
+ /**
579
+ * The first enabled option or Create row, else the first row (or -1). Select-all is skipped, so
580
+ * Enter after typing a term picks the first match rather than every match.
581
+ */
572
582
  function firstEnabledIndex(nav) {
573
- const index = nav.findIndex((item) => item.kind === "create" || !item.isDisabled);
583
+ const index = nav.findIndex((item) => item.kind === "create" || item.kind === "option" && !item.isDisabled);
574
584
  return index === -1 ? nav.length > 0 ? 0 : -1 : index;
575
585
  }
576
586
  /** The first selected option row, or -1. */
@@ -588,7 +598,7 @@ function openIndex(nav, target) {
588
598
  }
589
599
  /** The field, the trailing parts, the panel and the engine, wired to the nearest Field. */
590
600
  function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
591
- const { options, selectedIds, isMultiple, onOptionSelect, onClear, renderSelection, getSelectionText, limitReason, onCreate, size = "MEDIUM", variant = "PRIMARY", isGhost = false, label: _label, labelTooltip: _labelTooltip, description: _description, placeholder = "Select an option…", error, isError = false, isDisabled = false, isReadOnly = false, isRequired = false, fillWidth = false, ariaLabel, isOpen, defaultIsOpen = false, onOpenChange, isSearchable = false, onSearch, isClearable = false, isLoading = false, emptyMessage = "No results", onEndReached, renderOption, pinnedIds, placement = Placement.BOTTOM_START, id, "aria-describedby": ariaDescribedBy, className, style, ...rest } = rootProps;
601
+ const { options, selectedIds, isMultiple, onOptionSelect, onClear, renderSelection, getSelectionText, limitReason, onCreate, selectAllLabel, onSelectAll, size = "MEDIUM", variant = "PRIMARY", isGhost = false, label: _label, labelTooltip: _labelTooltip, description: _description, placeholder = "Select an option…", error, isError = false, isDisabled = false, isReadOnly = false, isRequired = false, fillWidth = false, ariaLabel, isOpen, defaultIsOpen = false, onOpenChange, isSearchable = false, onSearch, isClearable = false, isLoading = false, emptyMessage = "No results", onEndReached, renderOption, pinnedIds, placement = Placement.BOTTOM_START, id, "aria-describedby": ariaDescribedBy, className, style, ...rest } = rootProps;
592
602
  const isBare = useIsBareField();
593
603
  const { field, controlId, describedBy, isInvalid, isRequired: isRequiredState, hasName } = useFieldWiring({
594
604
  id,
@@ -641,7 +651,7 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
641
651
  }
642
652
  }, [unknownKey]);
643
653
  const selectionText = selectedOptions.length > 0 ? getSelectionText(selectedOptions) : "";
644
- const term = isSearchable ? normalizeTerm(rawTerm) : "";
654
+ const term = isSearchable ? normalizeSearchTerm(rawTerm) : "";
645
655
  const trimmedTerm = rawTerm.trim();
646
656
  const isFiltering = isSearchable && onSearch === void 0;
647
657
  const hasExactMatch = trimmedTerm !== "" && options.some((option) => option.label.toLocaleLowerCase() === trimmedTerm.toLocaleLowerCase());
@@ -653,6 +663,7 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
653
663
  selected: selectedSet,
654
664
  limitReason,
655
665
  createLabel: onCreate !== void 0 && isSearchable && trimmedTerm !== "" && !hasExactMatch && limitReason === void 0 ? trimmedTerm : null,
666
+ selectAllLabel: isMultiple && isFilled(selectAllLabel) ? selectAllLabel : null,
656
667
  isLoading,
657
668
  listboxId
658
669
  });
@@ -767,6 +778,10 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
767
778
  setRawTerm("");
768
779
  return;
769
780
  }
781
+ if (item.kind === "selectAll") {
782
+ onSelectAll?.(item.targetIds, item.state !== "checked");
783
+ return;
784
+ }
770
785
  if (item.isDisabled) return;
771
786
  onOptionSelect(item.option.id);
772
787
  if (!isMultiple) close();
@@ -1016,11 +1031,12 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
1016
1031
  children: placeholderText
1017
1032
  })
1018
1033
  });
1019
- /** One option or Create row. */
1034
+ /** One option, Create or select-all row. */
1020
1035
  const renderNavRow = (item, virtualStart) => {
1021
1036
  const isActive = item.navIndex === activeIndex;
1022
1037
  const isRowDisabled = item.kind === "option" && item.isDisabled;
1023
- const isSelected = item.kind === "option" && item.isSelected;
1038
+ const isSelected = item.kind === "option" && item.isSelected || item.kind === "selectAll" && item.state === "checked";
1039
+ const isMixed = item.kind === "selectAll" && item.state === "mixed";
1024
1040
  const virtualProps = virtualStart === void 0 ? {} : {
1025
1041
  "data-index": item.flatIndex,
1026
1042
  ref: virtualizer.measureElement,
@@ -1031,7 +1047,13 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
1031
1047
  } : {}
1032
1048
  };
1033
1049
  let content;
1034
- if (item.kind === "create") content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Icon, {
1050
+ if (item.kind === "selectAll") content = /* @__PURE__ */ jsx(Content, { children: /* @__PURE__ */ jsx(Text, {
1051
+ as: "span",
1052
+ size: rowTextSize,
1053
+ lineClamp: 1,
1054
+ children: item.label
1055
+ }) });
1056
+ else if (item.kind === "create") content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Icon, {
1035
1057
  component: PlusIcon,
1036
1058
  size: rowIconPx,
1037
1059
  variant: IconVariant.SECONDARY,
@@ -1083,6 +1105,7 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
1083
1105
  id: item.domId,
1084
1106
  role: "option",
1085
1107
  "aria-selected": isSelected,
1108
+ ...item.kind === "selectAll" ? { "aria-checked": isMixed ? "mixed" : isSelected } : {},
1086
1109
  ...isRowDisabled ? { "aria-disabled": true } : {},
1087
1110
  onPointerMove: () => {
1088
1111
  if (item.navIndex !== activeIndex) setActive(item.navIndex, false);
@@ -1091,8 +1114,8 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
1091
1114
  className: cx(SELECT_INPUT_SIZE_TO_ROW_CSS_MAP[size], isActive && ACTIVE_ROW_CSS, isActive && !isRowDisabled && ACTIVE_ROW_PRESS_CSS, isRowDisabled && ROW_DISABLED_CSS, virtualStart !== void 0 && VIRTUAL_ROW_CSS),
1092
1115
  children: [content, /* @__PURE__ */ jsx(CheckSlot, {
1093
1116
  className: SELECT_INPUT_SIZE_TO_CHECK_SLOT_CSS_MAP[size],
1094
- children: isSelected && /* @__PURE__ */ jsx(Icon, {
1095
- component: CheckIcon,
1117
+ children: (isSelected || isMixed) && /* @__PURE__ */ jsx(Icon, {
1118
+ component: isMixed ? MinusIcon : CheckIcon,
1096
1119
  size: rowIconPx,
1097
1120
  variant: isRowDisabled ? IconVariant.DISABLED : IconVariant.PRIMARY,
1098
1121
  "aria-hidden": true
@@ -1168,7 +1191,8 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
1168
1191
  };
1169
1192
  switch (item.kind) {
1170
1193
  case "option":
1171
- case "create": return renderNavRow(item, virtualItem.start);
1194
+ case "create":
1195
+ case "selectAll": return renderNavRow(item, virtualItem.start);
1172
1196
  case "heading": return renderHeading(item, virtual);
1173
1197
  case "separator": return renderSeparator(item.key, virtual);
1174
1198
  default: return renderStatus(item.kind, virtual);
@@ -1176,6 +1200,7 @@ function SelectInputFrame({ rootProps, forwardedRef, isRoot }) {
1176
1200
  })
1177
1201
  });
1178
1202
  else listContent = /* @__PURE__ */ jsxs(Fragment$1, { children: [
1203
+ model.selectAll !== null && renderNavRow(model.selectAll),
1179
1204
  model.pinned.map((item) => renderNavRow(item)),
1180
1205
  model.hasSeparator && renderSeparator("separator"),
1181
1206
  model.sections.map((section) => section.heading === null ? section.rows.map((item) => renderNavRow(item)) : /* @__PURE__ */ jsxs(GroupRoot, {
@@ -1351,6 +1376,8 @@ var SelectInputInner = forwardRef(function SelectInput({ value, defaultValue = n
1351
1376
  getSelectionText: (selected) => selected[0]?.label ?? "",
1352
1377
  limitReason: void 0,
1353
1378
  onCreate: void 0,
1379
+ selectAllLabel: void 0,
1380
+ onSelectAll: void 0,
1354
1381
  ...className === void 0 ? {} : { className },
1355
1382
  ...style === void 0 ? {} : { style }
1356
1383
  });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @internal The selection MultiSelectInput's select-all row produces. Private on purpose (an
3
+ * `internal/` module: built, never a package export).
4
+ */
5
+ /**
6
+ * The next selection after the select-all row is chosen. `shouldSelect` appends the covered ids
7
+ * that are not yet selected, in display order, after the current selection (selection order is
8
+ * kept) and stops at `limit` (`maxSelected`); otherwise it removes every covered id. Ids outside
9
+ * `ids` (options hidden by the search term) are never touched.
10
+ */
11
+ export declare function applySelectAll<TId extends string>(current: readonly TId[], ids: readonly TId[], shouldSelect: boolean, limit?: number): TId[];
@@ -0,0 +1,28 @@
1
+ //#region src/inputs/internal/selectAll.ts
2
+ /**
3
+ * @internal The selection MultiSelectInput's select-all row produces. Private on purpose (an
4
+ * `internal/` module: built, never a package export).
5
+ */
6
+ /**
7
+ * The next selection after the select-all row is chosen. `shouldSelect` appends the covered ids
8
+ * that are not yet selected, in display order, after the current selection (selection order is
9
+ * kept) and stops at `limit` (`maxSelected`); otherwise it removes every covered id. Ids outside
10
+ * `ids` (options hidden by the search term) are never touched.
11
+ */
12
+ function applySelectAll(current, ids, shouldSelect, limit) {
13
+ if (!shouldSelect) {
14
+ const covered = new Set(ids);
15
+ return current.filter((id) => !covered.has(id));
16
+ }
17
+ const next = [...current];
18
+ const selected = new Set(current);
19
+ for (const id of ids) {
20
+ if (limit !== void 0 && next.length >= limit) break;
21
+ if (selected.has(id)) continue;
22
+ selected.add(id);
23
+ next.push(id);
24
+ }
25
+ return next;
26
+ }
27
+ //#endregion
28
+ export { applySelectAll };
@@ -7,8 +7,9 @@ import { ButtonSize, ButtonVariant } from "../buttons/Button.js";
7
7
  import CopyButton from "../buttons/CopyButton.js";
8
8
  import Span, { SpanVariant } from "../text/Span.js";
9
9
  import { InputSize, InputVariant } from "../inputs/Input.js";
10
- import SearchInput from "../inputs/SearchInput.js";
11
10
  import CollapseMotion from "../transform/internal/CollapseMotion.js";
11
+ import { isSearchMatch, normalizeSearchTerm } from "../utils/search.js";
12
+ import SearchInput from "../inputs/SearchInput.js";
12
13
  import { TREE_ROW_CSS, TREE_ROW_LABEL_CSS, TREE_ROW_LEVEL_VARIABLE, TREE_ROW_SIZE_TO_CARET_PX_MAP, TREE_ROW_SIZE_TO_CSS_MAP, TREE_ROW_SIZE_TO_TEXT_SIZE_MAP, TreeRowCaretSlot, TreeRowSize } from "../tree/treeRow.js";
13
14
  /* empty css */
14
15
  import { styled } from "@linaria/react";
@@ -161,12 +162,12 @@ var searchTextOf = (node) => {
161
162
  const display = displayOf(node.value, node.kind);
162
163
  return node.kind === "STRING" ? display.slice(1, -1) : display;
163
164
  };
164
- /** Whether a node's key (not an index) or primitive value contains the lower-cased term. */
165
- var isMatch = (node, term) => typeof node.key === "string" && node.key.toLowerCase().includes(term) || (searchTextOf(node)?.toLowerCase().includes(term) ?? false);
165
+ /** Whether a node's key (not an index) or primitive value contains the term (the shared search rule). */
166
+ var isMatch = (node, term) => isSearchMatch(term, typeof node.key === "string" ? node.key : void 0, searchTextOf(node));
166
167
  /** Splits `text` around case-insensitive occurrences of `term`, marking each. */
167
168
  function highlight(text, term) {
168
169
  if (term === "") return text;
169
- const lower = text.toLowerCase();
170
+ const lower = text.toLocaleLowerCase();
170
171
  const parts = [];
171
172
  let from = 0;
172
173
  let at = lower.indexOf(term, from);
@@ -198,11 +199,11 @@ var JsonViewer = forwardRef(function JsonViewer(props, ref) {
198
199
  const [expanded, setExpanded] = useState(() => new Set(allNodes.filter((node) => node.isContainer && node.level <= defaultExpandedDepth).map((node) => node.path)));
199
200
  const [term, setTerm] = useState("");
200
201
  const preSearchExpanded = useRef(null);
201
- const lowerTerm = term.toLowerCase();
202
+ const lowerTerm = normalizeSearchTerm(term);
202
203
  const matchCount = useMemo(() => lowerTerm === "" ? 0 : allNodes.filter((node) => isMatch(node, lowerTerm)).length, [allNodes, lowerTerm]);
203
204
  /** Search drives expansion: the ancestors of every match open; clearing restores the set. */
204
205
  const handleSearch = (next) => {
205
- const lower = next.toLowerCase();
206
+ const lower = normalizeSearchTerm(next);
206
207
  if (lower === "") {
207
208
  if (preSearchExpanded.current !== null) setExpanded(preSearchExpanded.current);
208
209
  preSearchExpanded.current = null;
@@ -0,0 +1,20 @@
1
+ import { type AriaAttributes, type CSSProperties, type ReactNode } from "react";
2
+ /** The sidebar column's width in px, the same in every Galaxy module (Filament, Alchemy). */
3
+ export declare const APP_FRAME_SIDEBAR_WIDTH = 240;
4
+ export interface AppFrameProps extends AriaAttributes {
5
+ /** The sidebar column's content: a `SidebarNav` with no `header`. It fills the column. */
6
+ sidebar: ReactNode;
7
+ /** The page, usually a `PageLayout`. The column is a flex column that never overflows the frame. */
8
+ children: ReactNode;
9
+ /** DOM id on the root `<div>`. */
10
+ id?: string;
11
+ /** Any `data-*` attribute, forwarded to the root `<div>`. */
12
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
13
+ /** Extra class on the root `<div>`. */
14
+ className?: string;
15
+ /** Inline style on the root `<div>`; only for setting CSS custom properties. */
16
+ style?: CSSProperties;
17
+ }
18
+ /** A Galaxy module's frame: the sidebar column, a hairline, and the page. */
19
+ declare const AppFrame: import("react").ForwardRefExoticComponent<AppFrameProps & import("react").RefAttributes<HTMLDivElement>>;
20
+ export default AppFrame;
@@ -0,0 +1,71 @@
1
+ import { Orientation } from "../theme/enums.js";
2
+ import Divider from "./Divider.js";
3
+ /* empty css */
4
+ import { styled } from "@linaria/react";
5
+ import { forwardRef } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/layout/AppFrame.tsx
8
+ /**
9
+ * The product frame of a Galaxy module: a fixed sidebar column, a vertical hairline, and a content
10
+ * column that holds the page. It fills the box the host gives it (`height: 100%`) on
11
+ * `background.primary`, the sidebar's own fill, and clips: the page owns the one scrolling region.
12
+ *
13
+ * It is one of two frame systems, and they never mix:
14
+ *
15
+ * - **AppFrame + PageLayout + a header-less SidebarNav** (Filament, Alchemy): a module inside the
16
+ * Galaxy host. The sidebar column is inset `8 8 0` so, with SidebarNav's own 8px, the first item
17
+ * sits 16px from the top and both sides and lines up with the host's Home button. PageLayout's
18
+ * 64px header carries the page title.
19
+ * - **Topbar + a SidebarNav `header`** (the app template, standalone apps): both rows are 48px, so
20
+ * their bottom hairlines meet. That frame is composed from `Flex`, `SidebarNav` and `Topbar`.
21
+ *
22
+ * A SidebarNav `header` inside AppFrame would sit 8px below a Topbar line it never meets, so pass
23
+ * it a `footer` and its items only, and `hasDividers={false}` when the footer should run on
24
+ * without a line.
25
+ *
26
+ * There is no width prop (layout props live on Box, Flex, FlexItem and Grid): the sidebar is
27
+ * `APP_FRAME_SIDEBAR_WIDTH`, 240px, the width every Galaxy module uses. Not a page (`PageLayout`),
28
+ * not resizable panes (`ResizablePanels`).
29
+ */
30
+ /** The sidebar column's width in px, the same in every Galaxy module (Filament, Alchemy). */
31
+ var APP_FRAME_SIDEBAR_WIDTH = 240;
32
+ /** The frame: a row that fills the host's box on the sidebar's fill, clipped. */
33
+ var Root = /*#__PURE__*/ styled("div")({
34
+ name: "Root",
35
+ class: "gx-AppFrame-r1x0tg2p",
36
+ propsAsIs: false
37
+ });
38
+ /**
39
+ * The sidebar column: fixed width, inset `8 8 0`. With SidebarNav's own 8px the first item sits
40
+ * 16px from the top and both sides, on the Galaxy host's Home button (the host contract), and the
41
+ * footer runs to the bottom edge.
42
+ */
43
+ var Sidebar = /*#__PURE__*/ styled("div")({
44
+ name: "Sidebar",
45
+ class: "gx-AppFrame-Sidebar-s1mhncm4",
46
+ propsAsIs: false
47
+ });
48
+ /** The page column: takes the rest of the row and may shrink to nothing, so the page clips, not the frame. */
49
+ var Content = /*#__PURE__*/ styled("div")({
50
+ name: "Content",
51
+ class: "gx-AppFrame-Content-c1eeh9po",
52
+ propsAsIs: false
53
+ });
54
+ /** A Galaxy module's frame: the sidebar column, a hairline, and the page. */
55
+ var AppFrame = forwardRef(function AppFrame({ sidebar, children, id, className, style, ...rest }, ref) {
56
+ return /* @__PURE__ */ jsxs(Root, {
57
+ ...rest,
58
+ ref,
59
+ id,
60
+ className,
61
+ style,
62
+ children: [
63
+ /* @__PURE__ */ jsx(Sidebar, { children: sidebar }),
64
+ /* @__PURE__ */ jsx(Divider, { orientation: Orientation.VERTICAL }),
65
+ /* @__PURE__ */ jsx(Content, { children })
66
+ ]
67
+ });
68
+ });
69
+ AppFrame.displayName = "AppFrame";
70
+ //#endregion
71
+ export { APP_FRAME_SIDEBAR_WIDTH, AppFrame as default };
@@ -0,0 +1,53 @@
1
+ import type { Icon as PhosphorIcon } from "@phosphor-icons/react";
2
+ import { type AriaAttributes, type CSSProperties, type ReactNode } from "react";
3
+ /** Where the region sits (canonical rungs, subset); one-to-one with `EmptyStateSize`. */
4
+ export declare enum EmptyLayoutSize {
5
+ /** A small card or a side panel: EmptyState `SMALL`. */
6
+ SMALL = "SMALL",
7
+ /** A panel or a page section. The default: EmptyState `MEDIUM`. */
8
+ MEDIUM = "MEDIUM",
9
+ /** A whole page body: EmptyState `LARGE`. */
10
+ LARGE = "LARGE"
11
+ }
12
+ /** The mark above the header: an icon in a tile. */
13
+ interface EmptyLayoutIconProps {
14
+ /** Phosphor icon component (`TrayIcon`, not `<TrayIcon />`) shown in a small tile above the header. Not with `graphic`. */
15
+ icon?: PhosphorIcon;
16
+ /** A node of the app's own above the header, with no tile around it. Not with `icon`. */
17
+ graphic?: undefined;
18
+ }
19
+ /** An app illustration instead of the icon tile. */
20
+ interface EmptyLayoutGraphicProps {
21
+ /** Phosphor icon component (`TrayIcon`, not `<TrayIcon />`) shown in a small tile above the header. Not with `graphic`. */
22
+ icon?: undefined;
23
+ /** A node of the app's own above the header, with no tile around it. Not with `icon`. */
24
+ graphic: ReactNode;
25
+ }
26
+ interface EmptyLayoutBaseProps extends AriaAttributes {
27
+ /** One line that says what would be here ("No pipelines yet"). */
28
+ header: string;
29
+ /** Why it is empty or what to do next. Up to three short lines. */
30
+ description?: string;
31
+ /** The way out: usually one `PRIMARY` and one `SECONDARY` `Button`. */
32
+ actions?: ReactNode;
33
+ /**
34
+ * Where the region sits; picks the EmptyState size one-to-one.
35
+ * @default EmptyLayoutSize.MEDIUM
36
+ */
37
+ size?: EmptyLayoutSize;
38
+ /** Pass `"status"` when the region empties because of something the user did (a search), so it is announced. */
39
+ role?: "status";
40
+ /** DOM id on the root `<div>`. */
41
+ id?: string;
42
+ /** Any `data-*` attribute, forwarded to the root `<div>`. */
43
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
44
+ /** Extra class on the root `<div>`. */
45
+ className?: string;
46
+ /** Inline style on the root `<div>`; only for setting CSS custom properties. */
47
+ style?: CSSProperties;
48
+ }
49
+ /** EmptyLayout props. Passing both `icon` and `graphic` is a type error, as on EmptyState. */
50
+ export type EmptyLayoutProps = EmptyLayoutBaseProps & (EmptyLayoutIconProps | EmptyLayoutGraphicProps);
51
+ /** A region with nothing to show: an EmptyState, filled and centred. */
52
+ declare const EmptyLayout: import("react").ForwardRefExoticComponent<EmptyLayoutProps & import("react").RefAttributes<HTMLDivElement>>;
53
+ export default EmptyLayout;
@@ -0,0 +1,59 @@
1
+ import EmptyState, { EmptyStateSize } from "../feedback/EmptyState.js";
2
+ /* empty css */
3
+ import { styled } from "@linaria/react";
4
+ import { forwardRef } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/layout/EmptyLayout.tsx
7
+ /**
8
+ * A region with nothing to show, filled and centred: `EmptyState` sized for where it sits, in a
9
+ * box that fills its parent (a page body, a panel, a table area) and centres it both ways. A
10
+ * preset over EmptyState, the `Modal → ConfirmDialog` pattern: EmptyState centres only across, and
11
+ * a non-layout component may not take a height, so this is the one place that fills.
12
+ *
13
+ * Siblings: `ErrorLayout` (the region failed), `PendingLayout` (the region is loading). Inside a
14
+ * table body, a chart or a card, use `EmptyState` itself: those already size the region.
15
+ */
16
+ /** Where the region sits (canonical rungs, subset); one-to-one with `EmptyStateSize`. */
17
+ var EmptyLayoutSize = /* @__PURE__ */ function(EmptyLayoutSize) {
18
+ /** A small card or a side panel: EmptyState `SMALL`. */
19
+ EmptyLayoutSize["SMALL"] = "SMALL";
20
+ /** A panel or a page section. The default: EmptyState `MEDIUM`. */
21
+ EmptyLayoutSize["MEDIUM"] = "MEDIUM";
22
+ /** A whole page body: EmptyState `LARGE`. */
23
+ EmptyLayoutSize["LARGE"] = "LARGE";
24
+ return EmptyLayoutSize;
25
+ }({});
26
+ /** Fills the region and centres the empty state in it. */
27
+ var Root = /*#__PURE__*/ styled("div")({
28
+ name: "Root",
29
+ class: "gx-EmptyLayout-rxz77xy",
30
+ propsAsIs: false
31
+ });
32
+ /** One-to-one: the layout's size is the empty state's. */
33
+ var EMPTY_LAYOUT_SIZE_TO_EMPTY_STATE_SIZE_MAP = {
34
+ ["SMALL"]: EmptyStateSize.SMALL,
35
+ ["MEDIUM"]: EmptyStateSize.MEDIUM,
36
+ ["LARGE"]: EmptyStateSize.LARGE
37
+ };
38
+ /** A region with nothing to show: an EmptyState, filled and centred. */
39
+ var EmptyLayout = forwardRef(function EmptyLayout({ header, description, actions, icon, graphic, size = "MEDIUM", role, id, className, style, ...rest }, ref) {
40
+ const mark = graphic === void 0 ? icon === void 0 ? {} : { icon } : { graphic };
41
+ return /* @__PURE__ */ jsx(Root, {
42
+ ...rest,
43
+ ref,
44
+ id,
45
+ className,
46
+ style,
47
+ children: /* @__PURE__ */ jsx(EmptyState, {
48
+ ...mark,
49
+ size: EMPTY_LAYOUT_SIZE_TO_EMPTY_STATE_SIZE_MAP[size],
50
+ header,
51
+ ...description === void 0 ? {} : { description },
52
+ ...actions === void 0 ? {} : { actions },
53
+ ...role === void 0 ? {} : { role }
54
+ })
55
+ });
56
+ });
57
+ EmptyLayout.displayName = "EmptyLayout";
58
+ //#endregion
59
+ export { EmptyLayoutSize, EmptyLayout as default };
@@ -0,0 +1,42 @@
1
+ import type { Icon as PhosphorIcon } from "@phosphor-icons/react";
2
+ import { type AriaAttributes, type CSSProperties, type ReactNode } from "react";
3
+ /** Where the region sits (canonical rungs, subset); one-to-one with `EmptyStateSize`. */
4
+ export declare enum ErrorLayoutSize {
5
+ /** A small card or a side panel: EmptyState `SMALL`, caption detail. */
6
+ SMALL = "SMALL",
7
+ /** A panel or a page section. The default: EmptyState `MEDIUM`, caption detail. */
8
+ MEDIUM = "MEDIUM",
9
+ /** A whole page body: EmptyState `LARGE`, `body_sm` detail. */
10
+ LARGE = "LARGE"
11
+ }
12
+ export interface ErrorLayoutProps extends AriaAttributes {
13
+ /** One line that says what failed ("Couldn't load pipelines"). */
14
+ header: string;
15
+ /** What the reader can do about it, in plain words. */
16
+ description?: string;
17
+ /** The raw error message, under the state in mono, selectable. For a message worth copying, not for prose. */
18
+ detail?: string;
19
+ /** The way out: a "Try again" `Button`, and a link to help when there is one. */
20
+ actions?: ReactNode;
21
+ /**
22
+ * Replaces the icon in the tile. Pass the component (`PlugsIcon`), not an element.
23
+ * @default WarningCircleIcon
24
+ */
25
+ icon?: PhosphorIcon;
26
+ /**
27
+ * Where the region sits; picks the EmptyState size one-to-one.
28
+ * @default ErrorLayoutSize.MEDIUM
29
+ */
30
+ size?: ErrorLayoutSize;
31
+ /** DOM id on the root `<div>`. */
32
+ id?: string;
33
+ /** Any `data-*` attribute, forwarded to the root `<div>`. */
34
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
35
+ /** Extra class on the root `<div>`. */
36
+ className?: string;
37
+ /** Inline style on the root `<div>`; only for setting CSS custom properties. */
38
+ style?: CSSProperties;
39
+ }
40
+ /** A region that failed: an error EmptyState with an optional detail, filled, centred and announced. */
41
+ declare const ErrorLayout: import("react").ForwardRefExoticComponent<ErrorLayoutProps & import("react").RefAttributes<HTMLDivElement>>;
42
+ export default ErrorLayout;
@@ -0,0 +1,60 @@
1
+ import { EmptyStateSize } from "../feedback/EmptyState.js";
2
+ import { ErrorStateContent } from "./internal/errorState.js";
3
+ /* empty css */
4
+ import { styled } from "@linaria/react";
5
+ import { forwardRef } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/layout/ErrorLayout.tsx
8
+ /**
9
+ * A region that failed to load, filled and centred: an `ERROR` EmptyState (a `WarningCircleIcon`
10
+ * tile by default) with the way out in `actions`, and an optional `detail` (the raw message, mono
11
+ * and selectable, so it can be copied into a bug report). The stack is `role="alert"`, so a region
12
+ * that fails after it was on screen is announced. A preset over EmptyState, the
13
+ * `Modal → ConfirmDialog` pattern; an empty table body shows the same error state.
14
+ *
15
+ * Siblings: `EmptyLayout` (nothing to show), `PendingLayout` (still loading). A problem with the
16
+ * content on screen, which stays readable, is an `Alert`.
17
+ */
18
+ /** Where the region sits (canonical rungs, subset); one-to-one with `EmptyStateSize`. */
19
+ var ErrorLayoutSize = /* @__PURE__ */ function(ErrorLayoutSize) {
20
+ /** A small card or a side panel: EmptyState `SMALL`, caption detail. */
21
+ ErrorLayoutSize["SMALL"] = "SMALL";
22
+ /** A panel or a page section. The default: EmptyState `MEDIUM`, caption detail. */
23
+ ErrorLayoutSize["MEDIUM"] = "MEDIUM";
24
+ /** A whole page body: EmptyState `LARGE`, `body_sm` detail. */
25
+ ErrorLayoutSize["LARGE"] = "LARGE";
26
+ return ErrorLayoutSize;
27
+ }({});
28
+ /** Fills the region and centres the error state in it. */
29
+ var Root = /*#__PURE__*/ styled("div")({
30
+ name: "Root",
31
+ class: "gx-ErrorLayout-r17y69m",
32
+ propsAsIs: false
33
+ });
34
+ /** One-to-one: the layout's size is the empty state's. */
35
+ var ERROR_LAYOUT_SIZE_TO_EMPTY_STATE_SIZE_MAP = {
36
+ ["SMALL"]: EmptyStateSize.SMALL,
37
+ ["MEDIUM"]: EmptyStateSize.MEDIUM,
38
+ ["LARGE"]: EmptyStateSize.LARGE
39
+ };
40
+ /** A region that failed: an error EmptyState with an optional detail, filled, centred and announced. */
41
+ var ErrorLayout = forwardRef(function ErrorLayout({ header, description, detail, actions, icon, size = "MEDIUM", id, className, style, ...rest }, ref) {
42
+ return /* @__PURE__ */ jsx(Root, {
43
+ ...rest,
44
+ ref,
45
+ id,
46
+ className,
47
+ style,
48
+ children: /* @__PURE__ */ jsx(ErrorStateContent, {
49
+ size: ERROR_LAYOUT_SIZE_TO_EMPTY_STATE_SIZE_MAP[size],
50
+ icon,
51
+ header,
52
+ description,
53
+ detail,
54
+ actions
55
+ })
56
+ });
57
+ });
58
+ ErrorLayout.displayName = "ErrorLayout";
59
+ //#endregion
60
+ export { ErrorLayoutSize, ErrorLayout as default };
@@ -0,0 +1,26 @@
1
+ import { type AriaAttributes, type CSSProperties, type ReactNode } from "react";
2
+ export interface PageLayoutProps extends AriaAttributes {
3
+ /** The page title. A string renders as an `h1` (`HEADING_MD`, medium, one line); a node renders as is. It names the `<main>` landmark. */
4
+ header: ReactNode;
5
+ /** The page's actions, at the end of the header row: one `PRIMARY` button at most, the rest `SECONDARY` or `TERTIARY`. */
6
+ actions?: ReactNode;
7
+ /** A strip under the header's hairline, above the tabs: an `Alert isBanner`. It stays put while the body scrolls. */
8
+ banner?: ReactNode;
9
+ /** The page's sibling views: a `Tabs` row, under the banner. */
10
+ tabs?: ReactNode;
11
+ /** A padded row of filters, a search or view switches, under the tabs, with a hairline under it. */
12
+ toolbar?: ReactNode;
13
+ /** The page body, in a `<main>` on `background.base` that never scrolls: wrap scrolling content in a `ScrollArea`. */
14
+ children: ReactNode;
15
+ /** DOM id on the root `<div>`. */
16
+ id?: string;
17
+ /** Any `data-*` attribute, forwarded to the root `<div>`. */
18
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
19
+ /** Extra class on the root `<div>`. */
20
+ className?: string;
21
+ /** Inline style on the root `<div>`; only for setting CSS custom properties. */
22
+ style?: CSSProperties;
23
+ }
24
+ /** One page of a Galaxy module: header, hairline, banner, tabs, toolbar, then the body. */
25
+ declare const PageLayout: import("react").ForwardRefExoticComponent<PageLayoutProps & import("react").RefAttributes<HTMLDivElement>>;
26
+ export default PageLayout;