@canvas-components/react-components 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -495,9 +495,13 @@ function ReactPopup(props) {
495
495
  )
496
496
  }
497
497
  ),
498
- document.body
498
+ resolvePopupContainer(anchorRef?.current)
499
499
  );
500
500
  }
501
+ function resolvePopupContainer(anchor) {
502
+ const fullscreenElement = document.fullscreenElement;
503
+ return fullscreenElement instanceof HTMLElement && (!anchor || fullscreenElement.contains(anchor)) ? fullscreenElement : document.body;
504
+ }
501
505
  function resolvePopupPosition(params) {
502
506
  const {
503
507
  anchor,
@@ -7260,8 +7264,84 @@ function clamp3(value, min, max) {
7260
7264
  return Math.min(Math.max(value, min), max);
7261
7265
  }
7262
7266
 
7267
+ // src/components/cascader/cascader-selection.ts
7268
+ function createCascaderSelection(options) {
7269
+ const entries = /* @__PURE__ */ new Map();
7270
+ const visit = (items, parents, disabled = false) => items.map((item) => {
7271
+ const nodes = [...parents, item];
7272
+ const path = nodes.map((node) => node.value);
7273
+ const key = JSON.stringify(path);
7274
+ const entry = {
7275
+ path,
7276
+ children: [],
7277
+ leaves: [],
7278
+ disabled: disabled || !!item.disabled
7279
+ };
7280
+ entries.set(key, entry);
7281
+ entry.children = visit(item.children ?? [], nodes, entry.disabled);
7282
+ entry.leaves = entry.disabled ? [] : entry.children.length ? entry.children.flatMap((child) => child.leaves) : [key];
7283
+ return entry;
7284
+ });
7285
+ const roots = visit(options, []);
7286
+ const expand = (value) => {
7287
+ const checked = /* @__PURE__ */ new Set();
7288
+ const preserved = /* @__PURE__ */ new Map();
7289
+ for (const path of value) {
7290
+ const key = JSON.stringify(path);
7291
+ const entry = entries.get(key);
7292
+ if (!entry || entry.disabled || !entry.leaves.length)
7293
+ preserved.set(key, path);
7294
+ else entry.leaves.forEach((leaf) => checked.add(leaf));
7295
+ }
7296
+ return { checked, preserved };
7297
+ };
7298
+ const state = (entry, checked) => {
7299
+ const count = entry?.leaves.filter((leaf) => checked.has(leaf)).length ?? 0;
7300
+ return {
7301
+ checked: count > 0 && count === entry?.leaves.length,
7302
+ indeterminate: count > 0 && count < (entry?.leaves.length ?? 0)
7303
+ };
7304
+ };
7305
+ const collect = (checked, preserved, strategy) => {
7306
+ const paths = [];
7307
+ const walk = (entry) => {
7308
+ if (entry.disabled) return;
7309
+ if (state(entry, checked).checked && (strategy === "SHOW_PARENT" || !entry.children.length))
7310
+ paths.push(entry.path);
7311
+ else entry.children.forEach(walk);
7312
+ };
7313
+ roots.forEach(walk);
7314
+ return [...paths, ...preserved.values()];
7315
+ };
7316
+ return {
7317
+ /** 归一化回填值,去重并应用父级/末级策略。 */
7318
+ normalize(value, strategy) {
7319
+ const { checked, preserved } = expand(value);
7320
+ return collect(checked, preserved, strategy);
7321
+ },
7322
+ /** 计算某一路径的全选和半选状态。 */
7323
+ getState(value, path) {
7324
+ const { checked, preserved } = expand(value);
7325
+ if (preserved.has(JSON.stringify(path)))
7326
+ return { checked: true, indeterminate: false };
7327
+ return state(entries.get(JSON.stringify(path)), checked);
7328
+ },
7329
+ /** 切换某个节点以及全部可用后代,不修改入参。 */
7330
+ toggle(value, path, strategy) {
7331
+ const entry = entries.get(JSON.stringify(path));
7332
+ if (!entry || entry.disabled || !entry.leaves.length) return value;
7333
+ const { checked, preserved } = expand(value);
7334
+ const remove = state(entry, checked).checked;
7335
+ entry.leaves.forEach(
7336
+ (leaf) => remove ? checked.delete(leaf) : checked.add(leaf)
7337
+ );
7338
+ return collect(checked, preserved, strategy);
7339
+ }
7340
+ };
7341
+ }
7342
+
7263
7343
  // src/components/cascader/style.css
7264
- styleInject('@import "../../styles/variables.css";\n.canvas-react-cascader {\n width: 100%;\n min-width: 0;\n}\n.canvas-react-cascader > .canvas-react-input-affix-wrapper {\n width: 100%;\n}\n.canvas-react-cascader-menus {\n display: flex;\n max-width: calc(100vw - 24px);\n overflow: auto;\n color: var(--canvas-react-color-text);\n font-size: var(--canvas-react-font-size);\n}\n.canvas-react-cascader-menu {\n flex: 0 0 auto;\n min-width: 120px;\n max-width: 320px;\n max-height: min(256px, calc(100vh - 48px));\n overflow: auto;\n padding: 4px;\n}\n.canvas-react-cascader-menu + .canvas-react-cascader-menu {\n border-left: 1px solid var(--canvas-react-color-border);\n}\n.canvas-react-cascader-option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n padding: 5px 10px;\n line-height: 22px;\n border-radius: var(--canvas-react-radius-sm);\n cursor: pointer;\n white-space: nowrap;\n}\n.canvas-react-cascader-option[aria-selected=true] {\n color: var(--canvas-react-color-primary);\n background: var(--canvas-react-color-bg-active);\n}\n.canvas-react-cascader-option[data-active=true] {\n background: var(--canvas-react-color-bg-hover, #f5f5f5);\n}\n.canvas-react-cascader-option[aria-disabled=true] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.canvas-react-cascader-empty {\n padding: 8px 10px;\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-cascader-chevron {\n width: 7px;\n height: 7px;\n flex: 0 0 auto;\n margin-left: 8px;\n border-top: 1.5px solid #9ca3af;\n border-right: 1.5px solid #9ca3af;\n transform: rotate(45deg);\n transform-origin: center;\n transition: transform 0.18s ease, border-color 0.18s ease;\n}\n.canvas-react-cascader-option:not([aria-disabled=true]):hover .canvas-react-cascader-chevron,\n.canvas-react-cascader-option[aria-selected=true] .canvas-react-cascader-chevron {\n border-color: #6b7280;\n transform: rotate(135deg);\n}\n');
7344
+ styleInject('@import "../../styles/variables.css";\n.canvas-react-cascader {\n width: 100%;\n min-width: 0;\n}\n.canvas-react-cascader > .canvas-react-input-affix-wrapper {\n width: 100%;\n}\n.canvas-react-cascader-menus {\n display: flex;\n max-width: calc(100vw - 24px);\n overflow: auto;\n color: var(--canvas-react-color-text);\n font-size: var(--canvas-react-font-size);\n}\n.canvas-react-cascader-menu {\n flex: 0 0 auto;\n min-width: 120px;\n max-width: 320px;\n max-height: min(256px, calc(100vh - 48px));\n overflow: auto;\n padding: 4px;\n}\n.canvas-react-cascader-menu + .canvas-react-cascader-menu {\n border-left: 1px solid var(--canvas-react-color-border);\n}\n.canvas-react-cascader-option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n padding: 5px 10px;\n line-height: 22px;\n border-radius: var(--canvas-react-radius-sm);\n cursor: pointer;\n white-space: nowrap;\n}\n.canvas-react-cascader-check {\n display: inline-flex;\n flex: 0 0 auto;\n margin-right: -12px;\n}\n.canvas-react-cascader-check > * {\n pointer-events: none;\n}\n.canvas-react-cascader-option > span:not(.canvas-react-cascader-check):not(.canvas-react-cascader-chevron) {\n flex: 1;\n}\n.canvas-react-cascader-option[aria-selected=true] {\n color: var(--canvas-react-color-primary);\n background: var(--canvas-react-color-bg-active);\n}\n.canvas-react-cascader-option[data-active=true] {\n background: var(--canvas-react-color-bg-hover, #f5f5f5);\n}\n.canvas-react-cascader-option[aria-disabled=true] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.canvas-react-cascader-empty {\n padding: 8px 10px;\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-cascader-chevron {\n width: 7px;\n height: 7px;\n flex: 0 0 auto;\n margin-left: 8px;\n border-top: 1.5px solid #9ca3af;\n border-right: 1.5px solid #9ca3af;\n transform: rotate(45deg);\n transform-origin: center;\n transition: transform 0.18s ease, border-color 0.18s ease;\n}\n.canvas-react-cascader-option:not([aria-disabled=true]):hover .canvas-react-cascader-chevron,\n.canvas-react-cascader-option[data-expanded=true] .canvas-react-cascader-chevron {\n border-color: #6b7280;\n transform: rotate(135deg);\n}\n');
7265
7345
  function resolvePath(options, value) {
7266
7346
  const path = [];
7267
7347
  for (const part of value) {
@@ -7279,6 +7359,8 @@ function ReactCascader(props) {
7279
7359
  value = EMPTY_VALUE,
7280
7360
  options = EMPTY_OPTIONS,
7281
7361
  showSearch = false,
7362
+ multiple = false,
7363
+ showCheckedStrategy = "SHOW_PARENT",
7282
7364
  changeOnSelect = false,
7283
7365
  expandTrigger = "click",
7284
7366
  displayRender,
@@ -7296,7 +7378,13 @@ function ReactCascader(props) {
7296
7378
  const inputRef = React.useRef(null);
7297
7379
  const popupRef = React.useRef(null);
7298
7380
  const popupId = React.useId();
7299
- const selected = React.useMemo(() => resolvePath(options, value), [options, value]);
7381
+ const selection = React.useMemo(() => createCascaderSelection(options), [options]);
7382
+ const singleValue = multiple ? EMPTY_VALUE : value;
7383
+ const multipleValue = multiple ? value.filter(Array.isArray) : [];
7384
+ const selected = React.useMemo(
7385
+ () => resolvePath(options, singleValue),
7386
+ [options, singleValue]
7387
+ );
7300
7388
  const [path, setPath] = React.useState(selected);
7301
7389
  const [open, setOpen] = React.useState(false);
7302
7390
  const [keyword, setKeyword] = React.useState("");
@@ -7306,10 +7394,14 @@ function ReactCascader(props) {
7306
7394
  React.useEffect(() => {
7307
7395
  setPath(selected);
7308
7396
  }, [selected]);
7309
- const labels = value.map(
7310
- (part, index) => selected[index]?.label ?? String(part)
7311
- );
7312
- const text = displayRender?.(labels, selected) ?? labels.join(" / ");
7397
+ const displayPath = (valuePath) => {
7398
+ const nodes = resolvePath(options, valuePath);
7399
+ const labels = valuePath.map(
7400
+ (part, index) => nodes[index]?.label ?? String(part)
7401
+ );
7402
+ return displayRender?.(labels, nodes) ?? labels.join(" / ");
7403
+ };
7404
+ const text = multiple ? selection.normalize(multipleValue, showCheckedStrategy).map(displayPath).join("\uFF1B") : displayPath(singleValue);
7313
7405
  const columns = [options];
7314
7406
  for (const item of path) {
7315
7407
  if (!item.children?.length) break;
@@ -7323,21 +7415,37 @@ function ReactCascader(props) {
7323
7415
  for (const item of items) {
7324
7416
  if (item.disabled) continue;
7325
7417
  const next = [...parents, item];
7326
- if ((!item.children?.length || changeOnSelect) && next.map((part) => part.label).join(" / ").toLocaleLowerCase().includes(keyword.trim().toLocaleLowerCase()))
7418
+ if ((!item.children?.length || changeOnSelect || multiple) && next.map((part) => part.label).join(" / ").toLocaleLowerCase().includes(keyword.trim().toLocaleLowerCase()))
7327
7419
  matches.push(next);
7328
7420
  if (item.children?.length) visit(item.children, next);
7329
7421
  }
7330
7422
  };
7331
7423
  visit(options, []);
7332
7424
  return matches;
7333
- }, [options, keyword, changeOnSelect]);
7425
+ }, [options, keyword, changeOnSelect, multiple]);
7334
7426
  React.useEffect(() => {
7335
7427
  popupRef.current?.querySelector("[data-active='true']")?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
7336
7428
  }, [focus, open]);
7429
+ const toggle = (next, trigger) => {
7430
+ const updated = selection.toggle(
7431
+ multipleValue,
7432
+ next.map((item) => item.value),
7433
+ showCheckedStrategy
7434
+ );
7435
+ onChange?.(
7436
+ updated,
7437
+ updated.map((parts) => resolvePath(options, parts)),
7438
+ { trigger, complete: false }
7439
+ );
7440
+ };
7337
7441
  const choose = (next, trigger, fromSearch = false) => {
7338
7442
  const item = next[next.length - 1];
7339
7443
  if (!item || next.some((option) => option.disabled)) return;
7340
7444
  setPath(next);
7445
+ if (multiple) {
7446
+ if (!item.children?.length || fromSearch) toggle(next, trigger);
7447
+ return;
7448
+ }
7341
7449
  const complete = !item.children?.length || fromSearch;
7342
7450
  if (complete || changeOnSelect)
7343
7451
  onChange?.(
@@ -7354,7 +7462,7 @@ function ReactCascader(props) {
7354
7462
  const show = () => {
7355
7463
  if (!interactive) return;
7356
7464
  setOpen(true);
7357
- setPath(selected);
7465
+ if (!multiple) setPath(selected);
7358
7466
  };
7359
7467
  return /* @__PURE__ */ jsxRuntime.jsxs(
7360
7468
  "div",
@@ -7406,7 +7514,7 @@ function ReactCascader(props) {
7406
7514
  setTyping(false);
7407
7515
  return;
7408
7516
  }
7409
- if (![
7517
+ if (!(multiple && event.key === " " && (!showSearch || !typing)) && ![
7410
7518
  "ArrowDown",
7411
7519
  "ArrowUp",
7412
7520
  "ArrowRight",
@@ -7439,7 +7547,9 @@ function ReactCascader(props) {
7439
7547
  const item = items[focus.index];
7440
7548
  if (!item || item.disabled) return;
7441
7549
  const next = searching ? results[focus.index] : [...path.slice(0, column), item];
7442
- if (event.key === "Enter") choose(next, "keyboard", searching);
7550
+ if (multiple && (event.key === "Enter" || event.key === " "))
7551
+ toggle(next, "keyboard");
7552
+ else if (event.key === "Enter") choose(next, "keyboard", searching);
7443
7553
  else if (event.key !== "ArrowRight") return;
7444
7554
  if (!searching && item.children?.length) {
7445
7555
  setPath(next);
@@ -7536,20 +7646,26 @@ function ReactCascader(props) {
7536
7646
  "div",
7537
7647
  {
7538
7648
  role: "listbox",
7649
+ "aria-multiselectable": multiple || void 0,
7539
7650
  "aria-label": searching ? "\u641C\u7D22\u7ED3\u679C" : `\u7B2C ${column + 1} \u7EA7\u9009\u9879`,
7540
7651
  className: "canvas-react-cascader-menu",
7541
7652
  children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-react-cascader-empty", children: "\u6682\u65E0\u6570\u636E" }) : items.map((item, index) => {
7542
7653
  const next = searching ? results[index] : [...path.slice(0, column), item];
7543
7654
  const active = focus.column === column && focus.index === index;
7655
+ const checkedState = multiple ? selection.getState(
7656
+ multipleValue,
7657
+ next.map((node) => node.value)
7658
+ ) : { checked: false, indeterminate: false };
7544
7659
  return /* @__PURE__ */ jsxRuntime.jsxs(
7545
7660
  "div",
7546
7661
  {
7547
7662
  id: `${popupId}-${column}-${index}`,
7548
7663
  role: "option",
7549
- "aria-selected": path[column] === item,
7664
+ "aria-selected": multiple ? checkedState.checked : path[column] === item,
7550
7665
  "aria-disabled": !!item.disabled,
7551
7666
  "data-active": active,
7552
7667
  className: "canvas-react-cascader-option",
7668
+ "data-expanded": !!item.children?.length && path[column] === item,
7553
7669
  onMouseDown: (event) => event.preventDefault(),
7554
7670
  onMouseEnter: () => {
7555
7671
  if (item.disabled) return;
@@ -7562,6 +7678,32 @@ function ReactCascader(props) {
7562
7678
  choose(next, "mouse", searching);
7563
7679
  },
7564
7680
  children: [
7681
+ multiple ? /* @__PURE__ */ jsxRuntime.jsx(
7682
+ "span",
7683
+ {
7684
+ className: "canvas-react-cascader-check",
7685
+ role: "checkbox",
7686
+ "aria-checked": checkedState.indeterminate ? "mixed" : checkedState.checked,
7687
+ "aria-label": `\u9009\u62E9 ${next.map((node) => node.label).join(" / ")}`,
7688
+ "aria-disabled": !!item.disabled,
7689
+ onClick: (event) => {
7690
+ event.preventDefault();
7691
+ event.stopPropagation();
7692
+ if (!item.disabled) toggle(next, "mouse");
7693
+ },
7694
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7695
+ ReactCheckbox,
7696
+ {
7697
+ tabIndex: -1,
7698
+ "aria-hidden": "true",
7699
+ readOnly: true,
7700
+ checked: checkedState.checked,
7701
+ indeterminate: checkedState.indeterminate,
7702
+ disabled: item.disabled
7703
+ }
7704
+ )
7705
+ }
7706
+ ) : null,
7565
7707
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: searching ? next.map((part) => part.label).join(" / ") : item.label }),
7566
7708
  !searching && item.children?.length ? /* @__PURE__ */ jsxRuntime.jsx(
7567
7709
  "span",
@@ -7586,6 +7728,371 @@ function ReactCascader(props) {
7586
7728
  }
7587
7729
  );
7588
7730
  }
7731
+
7732
+ // src/components/tree-select/tree-select-utils.ts
7733
+ function createTreeSelectIndex(treeData) {
7734
+ const entries = [];
7735
+ const byValue = /* @__PURE__ */ new Map();
7736
+ const visit = (nodes, parents) => {
7737
+ for (const node of nodes) {
7738
+ const entry = {
7739
+ node,
7740
+ key: node.key ?? node.value,
7741
+ parents,
7742
+ depth: parents.length
7743
+ };
7744
+ entries.push(entry);
7745
+ byValue.set(node.value, entry);
7746
+ if (node.children?.length) visit(node.children, [...parents, entry]);
7747
+ }
7748
+ };
7749
+ visit(treeData, []);
7750
+ return { entries, byValue };
7751
+ }
7752
+ function normalizeTreeSelectValue(value, multiple) {
7753
+ const values = multiple ? Array.isArray(value) ? value : [] : [value];
7754
+ return [
7755
+ ...new Set(
7756
+ values.filter(
7757
+ (part) => typeof part === "string" || typeof part === "number"
7758
+ )
7759
+ )
7760
+ ];
7761
+ }
7762
+
7763
+ // src/components/tree-select/style.css
7764
+ styleInject('@import "../../styles/variables.css";\n.canvas-react-tree-select {\n width: 100%;\n min-width: 0;\n}\n.canvas-react-tree-select-tree {\n max-height: min(280px, calc(100vh - 48px));\n overflow: auto;\n padding: 4px;\n color: var(--canvas-react-color-text);\n font-size: var(--canvas-react-font-size);\n}\n.canvas-react-tree-select-node {\n display: flex;\n align-items: center;\n gap: 6px;\n min-height: 30px;\n padding: 2px 10px 2px 6px;\n box-sizing: border-box;\n line-height: 22px;\n border-radius: var(--canvas-react-radius-sm);\n cursor: pointer;\n}\n.canvas-react-tree-select-node[data-active=true] {\n background: var(--canvas-react-color-bg-hover);\n}\n.canvas-react-tree-select-node[aria-selected=true] {\n background: var(--canvas-react-color-bg-active);\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-tree-select-node[aria-disabled=true] .canvas-react-tree-select-title {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.canvas-react-tree-select-switcher {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 18px;\n width: 18px;\n height: 24px;\n border: 0;\n padding: 0;\n background: transparent;\n cursor: pointer;\n}\n.canvas-react-tree-select-chevron {\n width: 7px;\n height: 7px;\n border-top: 1.5px solid #9ca3af;\n border-right: 1.5px solid #9ca3af;\n transform: rotate(45deg);\n transition: transform 0.18s ease, border-color 0.18s ease;\n pointer-events: none;\n}\n.canvas-react-tree-select-chevron-open {\n transform: rotate(135deg);\n border-color: #6b7280;\n}\n.canvas-react-tree-select-title {\n flex: 1;\n white-space: nowrap;\n}\n.canvas-react-tree-select-check {\n color: var(--canvas-react-color-primary);\n}\n.canvas-react-tree-select-empty {\n padding: 8px 10px;\n color: var(--canvas-react-color-text-muted);\n}\n.canvas-react-tree-select:hover .canvas-react-select-clear,\n.canvas-react-tree-select-open .canvas-react-select-clear,\n.canvas-react-tree-select:focus-within .canvas-react-select-clear {\n opacity: 1;\n pointer-events: auto;\n}\n.canvas-react-tree-select:hover .canvas-react-select-actions-clearable .canvas-react-select-arrow,\n.canvas-react-tree-select-open .canvas-react-select-actions-clearable .canvas-react-select-arrow,\n.canvas-react-tree-select:focus-within .canvas-react-select-actions-clearable .canvas-react-select-arrow {\n opacity: 0;\n}\n');
7765
+ var EMPTY_TREE = [];
7766
+ function ReactTreeSelect(props) {
7767
+ const {
7768
+ value = null,
7769
+ treeData = EMPTY_TREE,
7770
+ multiple = false,
7771
+ showSearch = multiple,
7772
+ treeDefaultExpandAll = false,
7773
+ treeDefaultExpandedKeys,
7774
+ allowClear = true,
7775
+ onChange,
7776
+ disabled,
7777
+ readOnly,
7778
+ onFocus,
7779
+ onBlur,
7780
+ onClick,
7781
+ onKeyDown,
7782
+ ...inputProps
7783
+ } = props;
7784
+ const index = React.useMemo(() => createTreeSelectIndex(treeData), [treeData]);
7785
+ const selected = React.useMemo(
7786
+ () => normalizeTreeSelectValue(value, multiple),
7787
+ [value, multiple]
7788
+ );
7789
+ const selectedSet = React.useMemo(() => new Set(selected), [selected]);
7790
+ const [expanded, setExpanded] = React.useState(
7791
+ () => /* @__PURE__ */ new Set([
7792
+ ...treeDefaultExpandAll ? index.entries.filter((entry) => entry.node.children?.length).map((entry) => entry.key) : treeDefaultExpandedKeys ?? [],
7793
+ ...selected.flatMap(
7794
+ (part) => index.byValue.get(part)?.parents.map((parent) => parent.key) ?? []
7795
+ )
7796
+ ])
7797
+ );
7798
+ const [open, setOpen] = React.useState(false);
7799
+ const [keyword, setKeyword] = React.useState(null);
7800
+ const [active, setActive] = React.useState(
7801
+ selected[0] ?? null
7802
+ );
7803
+ const anchorRef = React.useRef(null);
7804
+ const inputRef = React.useRef(null);
7805
+ const treeRef = React.useRef(null);
7806
+ const treeId = React.useId();
7807
+ const interactive = !disabled && !readOnly;
7808
+ const query = (keyword ?? "").trim().toLocaleLowerCase();
7809
+ const text = selected.map((part) => index.byValue.get(part)?.node.title ?? String(part)).join("\uFF1B");
7810
+ const visible = React.useMemo(() => {
7811
+ if (!query)
7812
+ return index.entries.filter(
7813
+ (entry) => entry.parents.every((parent) => expanded.has(parent.key))
7814
+ );
7815
+ const matches = /* @__PURE__ */ new Set();
7816
+ for (const entry of index.entries) {
7817
+ if (!entry.node.title.toLocaleLowerCase().includes(query)) continue;
7818
+ matches.add(entry);
7819
+ entry.parents.forEach((parent) => matches.add(parent));
7820
+ }
7821
+ return index.entries.filter((entry) => matches.has(entry));
7822
+ }, [index, query, expanded]);
7823
+ React.useEffect(() => {
7824
+ setExpanded((previous) => {
7825
+ const next = new Set(previous);
7826
+ selected.forEach(
7827
+ (part) => index.byValue.get(part)?.parents.forEach((parent) => next.add(parent.key))
7828
+ );
7829
+ return next;
7830
+ });
7831
+ }, [index, selected]);
7832
+ React.useEffect(() => {
7833
+ treeRef.current?.querySelector('[data-active="true"]')?.scrollIntoView?.({ block: "nearest" });
7834
+ }, [active, open]);
7835
+ const expand = (entry, next) => {
7836
+ if (!entry.node.children?.length || query) return;
7837
+ setExpanded((previous) => {
7838
+ const keys = new Set(previous);
7839
+ if (next ?? !keys.has(entry.key)) keys.add(entry.key);
7840
+ else keys.delete(entry.key);
7841
+ return keys;
7842
+ });
7843
+ };
7844
+ const choose = (entry, trigger) => {
7845
+ if (entry.node.disabled) return;
7846
+ if (entry.node.selectable === false) {
7847
+ expand(entry);
7848
+ return;
7849
+ }
7850
+ const next = multiple ? selectedSet.has(entry.node.value) ? selected.filter((part) => part !== entry.node.value) : [...selected, entry.node.value] : [entry.node.value];
7851
+ onChange?.(
7852
+ multiple ? next : entry.node.value,
7853
+ next.flatMap((part) => index.byValue.get(part)?.node ?? []),
7854
+ { trigger, complete: !multiple }
7855
+ );
7856
+ if (!multiple) {
7857
+ setOpen(false);
7858
+ setKeyword(null);
7859
+ }
7860
+ };
7861
+ const show = () => {
7862
+ if (interactive) setOpen(true);
7863
+ };
7864
+ const activeIndex = visible.findIndex((entry) => entry.node.value === active);
7865
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7866
+ "div",
7867
+ {
7868
+ ref: anchorRef,
7869
+ className: `canvas-react-tree-select${open && interactive ? " canvas-react-tree-select-open" : ""}`,
7870
+ children: [
7871
+ /* @__PURE__ */ jsxRuntime.jsx(
7872
+ ReactInput,
7873
+ {
7874
+ ...inputProps,
7875
+ ref: inputRef,
7876
+ disabled,
7877
+ readOnly: readOnly || !showSearch,
7878
+ allowClear: false,
7879
+ role: "combobox",
7880
+ "aria-haspopup": "tree",
7881
+ "aria-expanded": open && interactive,
7882
+ "aria-controls": open ? treeId : void 0,
7883
+ "aria-activedescendant": open && activeIndex >= 0 ? `${treeId}-${activeIndex}` : void 0,
7884
+ value: open && keyword !== null ? keyword : text,
7885
+ onFocus: (event) => {
7886
+ show();
7887
+ onFocus?.(event);
7888
+ },
7889
+ onClick: (event) => {
7890
+ show();
7891
+ onClick?.(event);
7892
+ },
7893
+ onBlur: (event) => {
7894
+ setOpen(false);
7895
+ setKeyword(null);
7896
+ onBlur?.(event);
7897
+ },
7898
+ onChange: (event) => {
7899
+ setKeyword(event.target.value);
7900
+ setActive(null);
7901
+ show();
7902
+ },
7903
+ onKeyDown: (event) => {
7904
+ onKeyDown?.(event);
7905
+ if (event.defaultPrevented || !interactive) return;
7906
+ if (event.key === "Escape") {
7907
+ event.preventDefault();
7908
+ setOpen(false);
7909
+ setKeyword(null);
7910
+ return;
7911
+ }
7912
+ const space = event.key === " " && (!showSearch || keyword === null);
7913
+ if (!space && ![
7914
+ "ArrowDown",
7915
+ "ArrowUp",
7916
+ "ArrowLeft",
7917
+ "ArrowRight",
7918
+ "Enter",
7919
+ "Home",
7920
+ "End"
7921
+ ].includes(event.key))
7922
+ return;
7923
+ event.preventDefault();
7924
+ show();
7925
+ if (!visible.length) return;
7926
+ const entry = visible[activeIndex];
7927
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
7928
+ const next = activeIndex < 0 ? event.key === "ArrowDown" ? 0 : visible.length - 1 : (activeIndex + (event.key === "ArrowDown" ? 1 : -1) + visible.length) % visible.length;
7929
+ setActive(visible[next].node.value);
7930
+ } else if (event.key === "Home" || event.key === "End")
7931
+ setActive(
7932
+ visible[event.key === "Home" ? 0 : visible.length - 1].node.value
7933
+ );
7934
+ else if (entry) {
7935
+ if (event.key === "ArrowRight") {
7936
+ if (entry.node.children?.length && !expanded.has(entry.key) && !query)
7937
+ expand(entry, true);
7938
+ else if (visible[activeIndex + 1]?.depth > entry.depth)
7939
+ setActive(visible[activeIndex + 1].node.value);
7940
+ } else if (event.key === "ArrowLeft") {
7941
+ if (expanded.has(entry.key) && !query) expand(entry, false);
7942
+ else
7943
+ setActive(
7944
+ entry.parents[entry.parents.length - 1]?.node.value ?? entry.node.value
7945
+ );
7946
+ } else choose(entry, "keyboard");
7947
+ }
7948
+ },
7949
+ suffix: /* @__PURE__ */ jsxRuntime.jsxs(
7950
+ "span",
7951
+ {
7952
+ className: `canvas-react-select-actions${allowClear && interactive && selected.length ? " canvas-react-select-actions-clearable" : ""}`,
7953
+ onPointerDown: (event) => {
7954
+ if (interactive) {
7955
+ event.preventDefault();
7956
+ event.stopPropagation();
7957
+ }
7958
+ },
7959
+ onMouseDown: (event) => {
7960
+ if (interactive) {
7961
+ event.preventDefault();
7962
+ event.stopPropagation();
7963
+ }
7964
+ },
7965
+ onClick: (event) => {
7966
+ event.stopPropagation();
7967
+ if (interactive) {
7968
+ inputRef.current?.focus({ preventScroll: true });
7969
+ show();
7970
+ }
7971
+ },
7972
+ children: [
7973
+ allowClear && interactive && selected.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
7974
+ "button",
7975
+ {
7976
+ type: "button",
7977
+ tabIndex: -1,
7978
+ "aria-label": "\u6E05\u7A7A\u6811\u9009\u62E9",
7979
+ className: "canvas-react-select-clear",
7980
+ onClick: (event) => {
7981
+ event.preventDefault();
7982
+ event.stopPropagation();
7983
+ inputRef.current?.focus({ preventScroll: true });
7984
+ setKeyword(null);
7985
+ setActive(null);
7986
+ setOpen(true);
7987
+ onChange?.(multiple ? [] : null, [], {
7988
+ trigger: "clear",
7989
+ complete: false
7990
+ });
7991
+ },
7992
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7993
+ "span",
7994
+ {
7995
+ className: "canvas-react-select-clear-icon",
7996
+ "aria-hidden": "true"
7997
+ }
7998
+ )
7999
+ }
8000
+ ) : null,
8001
+ /* @__PURE__ */ jsxRuntime.jsx(
8002
+ "span",
8003
+ {
8004
+ className: `canvas-react-select-arrow${open ? " canvas-react-select-arrow-open" : ""}`,
8005
+ "aria-hidden": "true"
8006
+ }
8007
+ )
8008
+ ]
8009
+ }
8010
+ )
8011
+ }
8012
+ ),
8013
+ /* @__PURE__ */ jsxRuntime.jsx(
8014
+ ReactPopup,
8015
+ {
8016
+ open: open && interactive,
8017
+ anchorRef,
8018
+ onOpenChange: setOpen,
8019
+ matchAnchorWidth: true,
8020
+ constrainHeight: true,
8021
+ surfaceClassName: "canvas-react-tree-select-popup",
8022
+ children: /* @__PURE__ */ jsxRuntime.jsx(
8023
+ "div",
8024
+ {
8025
+ ref: treeRef,
8026
+ id: treeId,
8027
+ role: "tree",
8028
+ "aria-label": "\u6811\u9009\u62E9\u9009\u9879",
8029
+ "aria-multiselectable": multiple || void 0,
8030
+ className: "canvas-react-tree-select-tree",
8031
+ children: visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-react-tree-select-empty", children: "\u6682\u65E0\u6570\u636E" }) : visible.map((entry, position) => {
8032
+ const isExpanded = !!query || expanded.has(entry.key);
8033
+ const hasChildren = !!entry.node.children?.length;
8034
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8035
+ "div",
8036
+ {
8037
+ id: `${treeId}-${position}`,
8038
+ role: "treeitem",
8039
+ "aria-level": entry.depth + 1,
8040
+ "aria-expanded": hasChildren ? isExpanded : void 0,
8041
+ "aria-selected": selectedSet.has(entry.node.value),
8042
+ "aria-disabled": !!entry.node.disabled,
8043
+ "data-active": entry.node.value === active,
8044
+ className: "canvas-react-tree-select-node",
8045
+ style: { paddingLeft: 6 + entry.depth * 18 },
8046
+ onMouseDown: (event) => event.preventDefault(),
8047
+ onMouseEnter: () => setActive(entry.node.value),
8048
+ onClick: () => {
8049
+ setActive(entry.node.value);
8050
+ choose(entry, "mouse");
8051
+ },
8052
+ children: [
8053
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
8054
+ "button",
8055
+ {
8056
+ type: "button",
8057
+ tabIndex: -1,
8058
+ "aria-label": `${isExpanded ? "\u6536\u8D77" : "\u5C55\u5F00"} ${entry.node.title}`,
8059
+ className: "canvas-react-tree-select-switcher",
8060
+ onClick: (event) => {
8061
+ event.preventDefault();
8062
+ event.stopPropagation();
8063
+ expand(entry);
8064
+ },
8065
+ children: /* @__PURE__ */ jsxRuntime.jsx(
8066
+ "span",
8067
+ {
8068
+ className: `canvas-react-tree-select-chevron${isExpanded ? " canvas-react-tree-select-chevron-open" : ""}`,
8069
+ "aria-hidden": "true"
8070
+ }
8071
+ )
8072
+ }
8073
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "canvas-react-tree-select-switcher" }),
8074
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "canvas-react-tree-select-title", children: entry.node.title }),
8075
+ multiple && selectedSet.has(entry.node.value) ? /* @__PURE__ */ jsxRuntime.jsx(
8076
+ "span",
8077
+ {
8078
+ className: "canvas-react-tree-select-check",
8079
+ "aria-hidden": "true",
8080
+ children: "\u2713"
8081
+ }
8082
+ ) : null
8083
+ ]
8084
+ },
8085
+ JSON.stringify(entry.node.value)
8086
+ );
8087
+ })
8088
+ }
8089
+ )
8090
+ }
8091
+ )
8092
+ ]
8093
+ }
8094
+ );
8095
+ }
7589
8096
  var ReactAutoComplete = React.forwardRef(function ReactAutoComplete2(props, ref) {
7590
8097
  const {
7591
8098
  onChange,
@@ -8908,6 +9415,7 @@ exports.ReactTimePicker = ReactTimePicker;
8908
9415
  exports.ReactTimeRangePicker = ReactTimeRangePicker;
8909
9416
  exports.ReactTooltip = ReactTooltip;
8910
9417
  exports.ReactTransition = ReactTransition;
9418
+ exports.ReactTreeSelect = ReactTreeSelect;
8911
9419
  exports.ReactVirtualList = ReactVirtualList;
8912
9420
  exports.ReactYearPicker = ReactYearPicker;
8913
9421
  exports.reactMessage = reactMessage;