@mirohq/design-system-combobox 0.3.6 → 0.3.7

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/main.js CHANGED
@@ -16,11 +16,11 @@ var designSystemIcons = require('@mirohq/design-system-icons');
16
16
  var designSystemScrollArea = require('@mirohq/design-system-scroll-area');
17
17
  var designSystemBaseSelect = require('@mirohq/design-system-base-select');
18
18
  var designSystemPrimitive = require('@mirohq/design-system-primitive');
19
- var designSystemUseAriaDisabled = require('@mirohq/design-system-use-aria-disabled');
20
- var designSystemUseLayoutEffect = require('@mirohq/design-system-use-layout-effect');
21
19
  var reactDom = require('react-dom');
22
- var designSystemBaseButton = require('@mirohq/design-system-base-button');
23
- var designSystemStyles = require('@mirohq/design-system-styles');
20
+ var designSystemUseLayoutEffect = require('@mirohq/design-system-use-layout-effect');
21
+ var designSystemUseAriaDisabled = require('@mirohq/design-system-use-aria-disabled');
22
+ var designSystemUseId = require('@mirohq/design-system-use-id');
23
+ var designSystemChip = require('@mirohq/design-system-chip');
24
24
 
25
25
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
26
26
 
@@ -79,6 +79,10 @@ const StyledBaseInput = designSystemStitches.styled(designSystemBaseInput.BaseIn
79
79
  }
80
80
  });
81
81
 
82
+ function searchQueryMatch(displayedText, searchValue) {
83
+ return displayedText.toLowerCase().includes(searchValue.toLowerCase());
84
+ }
85
+
82
86
  const ComboboxContext = React.createContext({});
83
87
  const ComboboxProvider = ({
84
88
  children,
@@ -92,13 +96,12 @@ const ComboboxProvider = ({
92
96
  onValueChange,
93
97
  searchValue: searchValueProp,
94
98
  onSearchValueChange,
95
- autoFilter = true,
99
+ autoFilter,
96
100
  ...restProps
97
101
  }) => {
98
102
  const triggerRef = React.useRef(null);
99
103
  const inputRef = React.useRef(null);
100
104
  const contentRef = React.useRef(null);
101
- const [defaultValue, setDefaultValue] = React.useState(defaultValueProp);
102
105
  const [openState = false, setOpenState] = reactUseControllableState.useControllableState({
103
106
  prop: openProp,
104
107
  defaultProp: defaultOpen,
@@ -115,16 +118,23 @@ const ComboboxProvider = ({
115
118
  defaultProp: defaultValueProp,
116
119
  onChange: onValueChange
117
120
  });
118
- const [filteredItems, setFilteredItems] = React.useState(/* @__PURE__ */ new Set());
119
- const [searchValue, setSearchValue] = reactUseControllableState.useControllableState({
121
+ const [searchValue = "", setSearchValue] = reactUseControllableState.useControllableState({
120
122
  prop: searchValueProp,
121
123
  defaultProp: "",
122
124
  onChange: onSearchValueChange
123
125
  });
124
126
  const [size, setSize] = React.useState();
125
127
  const [placeholder, setPlaceholder] = React.useState();
126
- const [itemValueTextMap, setItemValueTextMap] = React.useState(/* @__PURE__ */ new Map());
128
+ const [itemsMap, setItemsMap] = React.useState(/* @__PURE__ */ new Map());
127
129
  const { valid: formFieldValid } = designSystemBaseForm.useFormFieldContext();
130
+ const filteredItems = React.useMemo(() => {
131
+ if (searchValue.length > 0) {
132
+ return Array.from(itemsMap.values()).filter(
133
+ (item) => searchQueryMatch(item.displayedText, searchValue)
134
+ );
135
+ }
136
+ return [];
137
+ }, [itemsMap, searchValue]);
128
138
  return /* @__PURE__ */ jsxRuntime.jsx(
129
139
  ComboboxContext.Provider,
130
140
  {
@@ -135,18 +145,15 @@ const ComboboxProvider = ({
135
145
  setOpenState,
136
146
  value,
137
147
  setValue,
138
- setDefaultValue,
139
- defaultValue,
140
148
  triggerRef,
141
149
  inputRef,
142
150
  contentRef,
143
151
  autoFilter,
144
152
  searchValue,
145
153
  setSearchValue,
154
+ itemsMap,
155
+ setItemsMap,
146
156
  filteredItems,
147
- setFilteredItems,
148
- itemValueTextMap,
149
- setItemValueTextMap,
150
157
  placeholder,
151
158
  setPlaceholder,
152
159
  size,
@@ -233,7 +240,7 @@ const Trigger = React__default["default"].forwardRef(
233
240
  placeholder,
234
241
  openActionLabel,
235
242
  closeActionLabel,
236
- clearable,
243
+ clearable = true,
237
244
  clearActionLabel,
238
245
  onChange,
239
246
  onFocus,
@@ -359,103 +366,6 @@ const StyledContent = designSystemStitches.styled(RadixPopover__namespace.Conten
359
366
  boxSizing: "border-box"
360
367
  });
361
368
 
362
- const StyledItem = designSystemStitches.styled(react.ComboboxItem, designSystemBaseSelect.itemStyles);
363
-
364
- const Item = React__default["default"].forwardRef(
365
- ({ disabled = false, value, textValue, children, ...restProps }, forwardRef) => {
366
- const { "aria-disabled": ariaDisabled, ...restAriaDisabledProps } = designSystemUseAriaDisabled.useAriaDisabled(restProps, { allowArrows: true });
367
- const {
368
- autoFilter,
369
- filteredItems,
370
- setItemValueTextMap,
371
- triggerRef,
372
- inputRef,
373
- value: comboboxValue = []
374
- } = useComboboxContext();
375
- designSystemUseLayoutEffect.useLayoutEffect(() => {
376
- const textToSet = textValue !== void 0 ? textValue : typeof children === "string" ? children : "";
377
- setItemValueTextMap((prevState) => new Map(prevState.set(value, textToSet)));
378
- return () => {
379
- setItemValueTextMap((prevState) => {
380
- prevState.delete(value);
381
- return new Map(prevState);
382
- });
383
- };
384
- }, [setItemValueTextMap, value, textValue, children]);
385
- if (autoFilter !== false && !filteredItems.has(value)) {
386
- return null;
387
- }
388
- const scrollIntoView = (event) => {
389
- var _a;
390
- if (((_a = inputRef == null ? void 0 : inputRef.current) == null ? void 0 : _a.parentElement) != null && (triggerRef == null ? void 0 : triggerRef.current) != null) {
391
- inputRef.current.parentElement.scrollTo({
392
- top: triggerRef.current.scrollHeight
393
- });
394
- }
395
- if (restProps.onClick !== void 0) {
396
- restProps.onClick(event);
397
- }
398
- };
399
- const isSelected = comboboxValue.includes(value);
400
- return /* @__PURE__ */ jsxRuntime.jsxs(
401
- StyledItem,
402
- {
403
- ...utils.mergeProps(restProps, restAriaDisabledProps),
404
- focusable: true,
405
- hideOnClick: false,
406
- accessibleWhenDisabled: designSystemUtils.booleanify(ariaDisabled),
407
- disabled: designSystemUtils.booleanify(ariaDisabled) || disabled,
408
- ref: forwardRef,
409
- value,
410
- onClick: scrollIntoView,
411
- "aria-selected": isSelected,
412
- children: [
413
- /* @__PURE__ */ jsxRuntime.jsx(
414
- react.ComboboxItemCheck,
415
- {
416
- checked: isSelected,
417
- render: ({ style, ...props }) => (
418
- // AriakitComboboxItemCheck adds its owm inline styles which we want to omit here
419
- /* @__PURE__ */ jsxRuntime.jsx(designSystemBaseSelect.StyledItemCheck, { ...props })
420
- ),
421
- children: /* @__PURE__ */ jsxRuntime.jsx(
422
- designSystemIcons.IconCheckMark,
423
- {
424
- size: "small",
425
- "data-testid": process.env.NODE_ENV === "test" ? "combobox-item-check" : void 0
426
- }
427
- )
428
- }
429
- ),
430
- children
431
- ]
432
- }
433
- );
434
- }
435
- );
436
-
437
- const itemType = React__default["default"].createElement(Item).type;
438
- const getChildrenItemValues = (componentChildren) => {
439
- const values = [];
440
- const recurse = (children) => {
441
- React__default["default"].Children.forEach(children, (child) => {
442
- if (!React__default["default"].isValidElement(child)) {
443
- return;
444
- }
445
- if (child.type === itemType) {
446
- const props = child.props;
447
- values.push(props.value);
448
- return;
449
- }
450
- if (child.props.children) {
451
- recurse(child.props.children);
452
- }
453
- });
454
- };
455
- recurse(componentChildren);
456
- return values;
457
- };
458
-
459
369
  const useDocumentFragment = () => {
460
370
  const [fragment, setFragment] = React__default["default"].useState();
461
371
  designSystemUseLayoutEffect.useLayoutEffect(() => {
@@ -493,23 +403,7 @@ const Content = React__default["default"].forwardRef(
493
403
  children,
494
404
  ...restProps
495
405
  }, forwardRef) => {
496
- const {
497
- triggerRef,
498
- contentRef,
499
- autoFilter,
500
- setFilteredItems,
501
- searchValue,
502
- direction,
503
- openState
504
- } = useComboboxContext();
505
- React.useEffect(() => {
506
- const childrenItemValues = getChildrenItemValues(children);
507
- const shouldFilter = autoFilter !== false && searchValue !== void 0 && searchValue.length > 0;
508
- const items = shouldFilter ? childrenItemValues.filter(
509
- (child) => child.toLowerCase().includes(searchValue.toLowerCase())
510
- ) : childrenItemValues;
511
- setFilteredItems(new Set(items));
512
- }, [children, autoFilter, setFilteredItems, searchValue]);
406
+ const { triggerRef, contentRef, direction, openState } = useComboboxContext();
513
407
  const getInvisibleContent = useInvisibleContent();
514
408
  if (!openState) {
515
409
  return getInvisibleContent(children);
@@ -555,84 +449,133 @@ const Content = React__default["default"].forwardRef(
555
449
  }
556
450
  );
557
451
 
452
+ const StyledItem = designSystemStitches.styled(react.ComboboxItem, designSystemBaseSelect.itemStyles);
453
+
454
+ const GroupContext = React.createContext({});
455
+ const GroupProvider = ({
456
+ children,
457
+ ...restProps
458
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
459
+ GroupContext.Provider,
460
+ {
461
+ value: {
462
+ ...restProps
463
+ },
464
+ children
465
+ }
466
+ );
467
+ const useGroupContext = () => React.useContext(GroupContext);
468
+
469
+ const Item = React__default["default"].forwardRef(
470
+ ({ disabled = false, value, textValue, children, ...restProps }, forwardRef) => {
471
+ const { "aria-disabled": ariaDisabled, ...restAriaDisabledProps } = designSystemUseAriaDisabled.useAriaDisabled(restProps, { allowArrows: true });
472
+ const {
473
+ searchValue,
474
+ autoFilter,
475
+ setItemsMap,
476
+ triggerRef,
477
+ inputRef,
478
+ value: comboboxValue = []
479
+ } = useComboboxContext();
480
+ const { groupId } = useGroupContext();
481
+ const displayedText = React.useMemo(() => {
482
+ if (textValue !== void 0) {
483
+ return textValue;
484
+ }
485
+ return typeof children === "string" ? children : "";
486
+ }, [textValue, children]);
487
+ designSystemUseLayoutEffect.useLayoutEffect(() => {
488
+ setItemsMap(
489
+ (prevState) => new Map(prevState.set(value, { displayedText, groupId }))
490
+ );
491
+ return () => {
492
+ setItemsMap((prevState) => {
493
+ prevState.delete(value);
494
+ return new Map(prevState);
495
+ });
496
+ };
497
+ }, [setItemsMap, groupId, value, displayedText]);
498
+ if (autoFilter && searchValue.length > 0 && !searchQueryMatch(displayedText, searchValue)) {
499
+ return null;
500
+ }
501
+ const scrollIntoView = (event) => {
502
+ var _a;
503
+ if (((_a = inputRef == null ? void 0 : inputRef.current) == null ? void 0 : _a.parentElement) != null && (triggerRef == null ? void 0 : triggerRef.current) != null) {
504
+ inputRef.current.parentElement.scrollTo({
505
+ top: triggerRef.current.scrollHeight
506
+ });
507
+ }
508
+ if (restProps.onClick !== void 0) {
509
+ restProps.onClick(event);
510
+ }
511
+ };
512
+ const isSelected = comboboxValue.includes(value);
513
+ return /* @__PURE__ */ jsxRuntime.jsxs(
514
+ StyledItem,
515
+ {
516
+ ...utils.mergeProps(restProps, restAriaDisabledProps),
517
+ focusable: true,
518
+ hideOnClick: false,
519
+ accessibleWhenDisabled: designSystemUtils.booleanify(ariaDisabled),
520
+ disabled: designSystemUtils.booleanify(ariaDisabled) || disabled,
521
+ ref: forwardRef,
522
+ value,
523
+ onClick: scrollIntoView,
524
+ "aria-selected": isSelected,
525
+ children: [
526
+ /* @__PURE__ */ jsxRuntime.jsx(
527
+ react.ComboboxItemCheck,
528
+ {
529
+ checked: isSelected,
530
+ render: ({ style, ...props }) => (
531
+ // AriakitComboboxItemCheck adds its owm inline styles which we want to omit here
532
+ /* @__PURE__ */ jsxRuntime.jsx(designSystemBaseSelect.StyledItemCheck, { ...props })
533
+ ),
534
+ children: /* @__PURE__ */ jsxRuntime.jsx(
535
+ designSystemIcons.IconCheckMark,
536
+ {
537
+ size: "small",
538
+ "data-testid": process.env.NODE_ENV === "test" ? "combobox-item-check" : void 0
539
+ }
540
+ )
541
+ }
542
+ ),
543
+ children
544
+ ]
545
+ }
546
+ );
547
+ }
548
+ );
549
+
558
550
  const Portal = (props) => /* @__PURE__ */ jsxRuntime.jsx(RadixPopover.Portal, { ...props });
559
551
 
560
552
  const StyledGroup = designSystemStitches.styled(react.Group, {});
561
553
 
562
554
  const Group = React__default["default"].forwardRef(({ children, ...rest }, forwardRef) => {
563
- const { autoFilter, filteredItems } = useComboboxContext();
564
- const childValues = React.useMemo(
565
- // don't perform calculation if auto filter is disabled
566
- () => autoFilter !== false ? getChildrenItemValues(children) : [],
567
- [children, autoFilter]
568
- );
569
- const hasVisibleChildren = React.useMemo(
570
- () => (
571
- // don't perform calculation if auto filter is disabled
572
- autoFilter !== false ? childValues.some((value) => filteredItems.has(value)) : true
573
- ),
574
- [childValues, filteredItems, autoFilter]
575
- );
555
+ const { autoFilter, searchValue, filteredItems } = useComboboxContext();
556
+ const id = designSystemUseId.useId();
576
557
  const getInvisibleContent = useInvisibleContent();
577
- if (!hasVisibleChildren) {
578
- return getInvisibleContent(children);
558
+ let hasVisibleContent = true;
559
+ if (autoFilter && searchValue.length > 0) {
560
+ hasVisibleContent = filteredItems.some((item) => item.groupId === id);
579
561
  }
580
- return /* @__PURE__ */ jsxRuntime.jsx(StyledGroup, { ...rest, ref: forwardRef, children });
562
+ return /* @__PURE__ */ jsxRuntime.jsx(GroupProvider, { groupId: id, children: hasVisibleContent ? /* @__PURE__ */ jsxRuntime.jsx(StyledGroup, { ...rest, ref: forwardRef, children }) : getInvisibleContent(children) });
581
563
  });
582
564
 
583
565
  const StyledGroupLabel = designSystemStitches.styled(react.GroupLabel, designSystemBaseSelect.groupLabelStyles);
584
566
 
585
567
  const GroupLabel = React__default["default"].forwardRef((props, forwardRef) => /* @__PURE__ */ jsxRuntime.jsx(StyledGroupLabel, { ...props, ref: forwardRef }));
586
568
 
587
- const StyledChip = designSystemStitches.styled(designSystemPrimitive.Primitive.div, {
588
- fontSize: "$150",
589
- padding: "$50 $100",
590
- borderRadius: "$round",
591
- display: "flex",
592
- alignItems: "center",
593
- gap: "$50",
594
- whiteSpace: "nowrap",
595
- maxWidth: "$35",
596
- backgroundColor: "$background-neutrals-subtle",
597
- color: "$text-neutrals"
598
- });
599
- const StyledChipButton = designSystemStitches.styled(designSystemBaseButton.BaseButton, {
600
- color: "$icon-neutrals-inactive",
601
- ...designSystemStyles.focus.css({
602
- boxShadow: "$focus-small-outline"
603
- })
604
- });
605
- const StyledChipContent = designSystemStitches.styled(designSystemPrimitive.Primitive.div, {
606
- textOverflow: "ellipsis",
607
- whiteSpace: "nowrap",
608
- overflow: "hidden",
609
- lineHeight: 1.3
610
- });
611
-
612
- const StyledLeftSlot = designSystemStitches.styled(designSystemPrimitive.Primitive.span, {
613
- order: -1,
614
- marginRight: "$50"
615
- });
616
-
617
- const LeftSlot = StyledLeftSlot;
618
-
619
- const Chip = React__default["default"].forwardRef(
620
- ({ children, disabled = false, onRemove, removeAriaLabel, ...restProps }, forwardRef) => /* @__PURE__ */ jsxRuntime.jsxs(StyledChip, { ...restProps, ref: forwardRef, children: [
621
- /* @__PURE__ */ jsxRuntime.jsx(StyledChipContent, { children }),
622
- !designSystemUtils.booleanify(disabled) && /* @__PURE__ */ jsxRuntime.jsx(StyledChipButton, { onClick: onRemove, "aria-label": removeAriaLabel, children: /* @__PURE__ */ jsxRuntime.jsx(designSystemIcons.IconCross, { size: "small", weight: "thin", "aria-hidden": true }) })
623
- ] })
624
- );
625
- Chip.LeftSlot = LeftSlot;
626
-
627
569
  const Value = ({ unselectAriaLabel }) => {
628
570
  const {
629
571
  value = [],
630
572
  setValue,
631
573
  disabled,
574
+ readOnly,
632
575
  "aria-disabled": ariaDisabled,
633
- itemValueTextMap
576
+ itemsMap
634
577
  } = useComboboxContext();
635
- const isDisabled = ariaDisabled === true || disabled;
578
+ const canRemoveItem = !designSystemUtils.booleanify(ariaDisabled) && !designSystemUtils.booleanify(disabled) && !designSystemUtils.booleanify(readOnly);
636
579
  const onItemRemove = React.useCallback(
637
580
  (item) => {
638
581
  setValue((prevValue) => prevValue == null ? void 0 : prevValue.filter((value2) => value2 !== item));
@@ -641,26 +584,26 @@ const Value = ({ unselectAriaLabel }) => {
641
584
  );
642
585
  const getItemText = React.useCallback(
643
586
  (itemValue) => {
644
- const textValue = itemValueTextMap.get(itemValue);
645
- if (textValue === void 0 || textValue === "") {
587
+ const itemData = itemsMap.get(itemValue);
588
+ if (itemData === void 0 || itemData.displayedText === "") {
646
589
  return null;
647
590
  }
648
591
  return /* @__PURE__ */ jsxRuntime.jsx(
649
- Chip,
592
+ designSystemChip.Chip,
650
593
  {
651
594
  onRemove: (e) => {
652
595
  onItemRemove(itemValue);
653
596
  e.stopPropagation();
654
597
  },
655
- disabled: isDisabled,
656
- removeAriaLabel: "".concat(unselectAriaLabel, " ").concat(textValue),
598
+ removable: canRemoveItem,
599
+ removeAriaLabel: "".concat(unselectAriaLabel, " ").concat(itemData.displayedText),
657
600
  "data-testid": process.env.NODE_ENV === "test" ? "combobox-value-".concat(itemValue) : void 0,
658
- children: textValue
601
+ children: itemData.displayedText
659
602
  },
660
603
  itemValue
661
604
  );
662
605
  },
663
- [isDisabled, itemValueTextMap, onItemRemove, unselectAriaLabel]
606
+ [canRemoveItem, itemsMap, onItemRemove, unselectAriaLabel]
664
607
  );
665
608
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: value.map(getItemText) });
666
609
  };
@@ -669,7 +612,7 @@ const StyledSeparator = designSystemStitches.styled(designSystemPrimitive.Primit
669
612
 
670
613
  const Separator = React__default["default"].forwardRef((props, forwardRef) => {
671
614
  const { autoFilter, searchValue } = useComboboxContext();
672
- if (autoFilter === true && searchValue !== void 0 && searchValue.length > 0) {
615
+ if (autoFilter && searchValue.length > 0) {
673
616
  return null;
674
617
  }
675
618
  return /* @__PURE__ */ jsxRuntime.jsx(StyledSeparator, { ...props, ref: forwardRef, "aria-hidden": true });
@@ -689,11 +632,13 @@ const StyledNoResult = designSystemStitches.styled(designSystemPrimitive.Primiti
689
632
  });
690
633
 
691
634
  const NoResult = React__default["default"].forwardRef((props, forwardRef) => {
692
- const { filteredItems } = useComboboxContext();
693
- if (filteredItems.size !== 0) {
694
- return null;
635
+ const { autoFilter, searchValue, filteredItems, itemsMap } = useComboboxContext();
636
+ const noActiveFiltering = !autoFilter || autoFilter && searchValue.length === 0;
637
+ const isVisible = noActiveFiltering ? itemsMap.size === 0 : filteredItems.length === 0;
638
+ if (isVisible) {
639
+ return /* @__PURE__ */ jsxRuntime.jsx(StyledNoResult, { ...props, ref: forwardRef });
695
640
  }
696
- return /* @__PURE__ */ jsxRuntime.jsx(StyledNoResult, { ...props, ref: forwardRef });
641
+ return null;
697
642
  });
698
643
 
699
644
  const Root = React__default["default"].forwardRef(
@@ -702,7 +647,6 @@ const Root = React__default["default"].forwardRef(
702
647
  const {
703
648
  openState,
704
649
  setOpenState,
705
- defaultValue,
706
650
  value = [],
707
651
  setValue,
708
652
  required,
@@ -756,7 +700,6 @@ const Root = React__default["default"].forwardRef(
756
700
  {
757
701
  open: openState,
758
702
  setOpen: onOpenChange,
759
- defaultSelectedValue: defaultValue,
760
703
  selectedValue: value,
761
704
  setSelectedValue: onSetSelectedValue,
762
705
  children: /* @__PURE__ */ jsxRuntime.jsxs(