@idds/react 1.5.44 → 1.5.45

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.es.js CHANGED
@@ -2397,6 +2397,13 @@ function BasicDropdown({
2397
2397
  if (disabled) return;
2398
2398
  handleOpenChange(!isOpen);
2399
2399
  };
2400
+ const handleKeyDown = (e) => {
2401
+ if (disabled) return;
2402
+ if (e.key === "Enter" || e.key === " ") {
2403
+ e.preventDefault();
2404
+ handleOpenChange(!isOpen);
2405
+ }
2406
+ };
2400
2407
  useEffect(() => {
2401
2408
  const handleClickOutside = (event) => {
2402
2409
  if (containerRef.current && !containerRef.current.contains(event.target)) {
@@ -2405,11 +2412,18 @@ function BasicDropdown({
2405
2412
  }
2406
2413
  }
2407
2414
  };
2415
+ const handleEscape = (event) => {
2416
+ if (event.key === "Escape" && isOpen) {
2417
+ handleOpenChange(false);
2418
+ }
2419
+ };
2408
2420
  document.addEventListener("mousedown", handleClickOutside);
2409
2421
  document.addEventListener("touchstart", handleClickOutside);
2422
+ document.addEventListener("keydown", handleEscape);
2410
2423
  return () => {
2411
2424
  document.removeEventListener("mousedown", handleClickOutside);
2412
2425
  document.removeEventListener("touchstart", handleClickOutside);
2426
+ document.removeEventListener("keydown", handleEscape);
2413
2427
  };
2414
2428
  }, [isOpen, onOpenChange]);
2415
2429
  const isPrimitiveTrigger = typeof trigger === "string" || typeof trigger === "number";
@@ -2420,11 +2434,18 @@ function BasicDropdown({
2420
2434
  ref: triggerRef,
2421
2435
  className: clsx("ina-basic-dropdown__trigger", triggerClassName),
2422
2436
  onClick: toggle,
2437
+ tabIndex: !isPrimitiveTrigger ? 0 : -1,
2438
+ role: !isPrimitiveTrigger ? "button" : void 0,
2439
+ "aria-haspopup": !isPrimitiveTrigger ? "true" : void 0,
2440
+ "aria-expanded": !isPrimitiveTrigger ? isOpen : void 0,
2441
+ onKeyDown: !isPrimitiveTrigger ? handleKeyDown : void 0,
2423
2442
  children: isPrimitiveTrigger ? /* @__PURE__ */ jsxs(
2424
2443
  "button",
2425
2444
  {
2426
2445
  type: "button",
2427
2446
  disabled,
2447
+ "aria-haspopup": "true",
2448
+ "aria-expanded": isOpen,
2428
2449
  className: clsx(
2429
2450
  "ina-basic-dropdown__trigger-button",
2430
2451
  isOpen && "ina-basic-dropdown__trigger-button--open"
@@ -3658,10 +3679,12 @@ function Modal({
3658
3679
  const [isVisible, setIsVisible] = useState(false);
3659
3680
  const closeBtnRef = useRef(null);
3660
3681
  const labelId = useRef(`modal-title-${Math.random().toString(36).slice(2)}`);
3682
+ const previousFocus = useRef(null);
3661
3683
  useEffect(() => setMounted(true), []);
3662
3684
  useEffect(() => {
3663
3685
  if (open) {
3664
3686
  setShouldRender(true);
3687
+ previousFocus.current = document.activeElement;
3665
3688
  requestAnimationFrame(() => {
3666
3689
  requestAnimationFrame(() => {
3667
3690
  setIsVisible(true);
@@ -3669,6 +3692,10 @@ function Modal({
3669
3692
  });
3670
3693
  } else {
3671
3694
  setIsVisible(false);
3695
+ if (previousFocus.current) {
3696
+ previousFocus.current.focus();
3697
+ previousFocus.current = null;
3698
+ }
3672
3699
  const timer = setTimeout(() => {
3673
3700
  setShouldRender(false);
3674
3701
  }, 300);
@@ -3686,13 +3713,39 @@ function Modal({
3686
3713
  }
3687
3714
  }, [shouldRender, mounted]);
3688
3715
  useEffect(() => {
3689
- var _a;
3690
3716
  if (!shouldRender || !mounted) return;
3691
3717
  if (isVisible) {
3692
- (_a = closeBtnRef.current) == null ? void 0 : _a.focus();
3718
+ setTimeout(() => {
3719
+ var _a;
3720
+ (_a = closeBtnRef.current) == null ? void 0 : _a.focus();
3721
+ }, 50);
3693
3722
  }
3694
3723
  const onKey = (e) => {
3695
- if (e.key === "Escape" && isVisible) onClose();
3724
+ if (e.key === "Escape" && isVisible) {
3725
+ onClose();
3726
+ return;
3727
+ }
3728
+ if (e.key === "Tab" && isVisible) {
3729
+ const modalElement = document.querySelector(".ina-modal__dialog");
3730
+ if (!modalElement) return;
3731
+ const focusableElements = modalElement.querySelectorAll(
3732
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
3733
+ );
3734
+ if (focusableElements.length === 0) return;
3735
+ const firstElement = focusableElements[0];
3736
+ const lastElement = focusableElements[focusableElements.length - 1];
3737
+ if (e.shiftKey) {
3738
+ if (document.activeElement === firstElement) {
3739
+ e.preventDefault();
3740
+ lastElement.focus();
3741
+ }
3742
+ } else {
3743
+ if (document.activeElement === lastElement) {
3744
+ e.preventDefault();
3745
+ firstElement.focus();
3746
+ }
3747
+ }
3748
+ }
3696
3749
  };
3697
3750
  document.addEventListener("keydown", onKey);
3698
3751
  return () => document.removeEventListener("keydown", onKey);
@@ -5278,10 +5331,13 @@ const SelectDropdown = ({
5278
5331
  required,
5279
5332
  selectionTitle
5280
5333
  }) => {
5334
+ var _a;
5281
5335
  const [isOpen, setIsOpen] = useState(false);
5282
5336
  const [internalSearchTerm, setInternalSearchTerm] = useState("");
5337
+ const [focusedIndex, setFocusedIndex] = useState(-1);
5283
5338
  const containerRef = useRef(null);
5284
5339
  const scrollContainerRef = useRef(null);
5340
+ const optionsRefs = useRef([]);
5285
5341
  const searchTimeoutRef = useRef(null);
5286
5342
  const selectedLabelsCacheRef = useRef(/* @__PURE__ */ new Map());
5287
5343
  const isSearchControlled = searchValue !== void 0;
@@ -5308,19 +5364,11 @@ const SelectDropdown = ({
5308
5364
  setInternalSearchTerm(searchValue);
5309
5365
  }
5310
5366
  }, [searchValue, isSearchControlled, internalSearchTerm]);
5311
- const handleLoadMoreClick = () => {
5312
- if (disabled || !hasMore || loading || !onLoadMore) return;
5313
- onLoadMore(currentPage + 1);
5314
- };
5315
5367
  useEffect(() => {
5316
5368
  const handleClickOutside = (e) => {
5317
5369
  if (containerRef.current && !containerRef.current.contains(e.target)) {
5318
5370
  setIsOpen(false);
5319
- if (isSearchControlled) {
5320
- onSearchChange == null ? void 0 : onSearchChange("");
5321
- } else {
5322
- setInternalSearchTerm("");
5323
- }
5371
+ setFocusedIndex(-1);
5324
5372
  onClose == null ? void 0 : onClose();
5325
5373
  }
5326
5374
  };
@@ -5423,6 +5471,18 @@ const SelectDropdown = ({
5423
5471
  const filteredOptions = onSearch ? options : !searchable ? options : options.filter(
5424
5472
  (o) => o.label.toLowerCase().includes(searchTerm.toLowerCase())
5425
5473
  );
5474
+ useEffect(() => {
5475
+ setFocusedIndex(-1);
5476
+ }, [filteredOptions]);
5477
+ useEffect(() => {
5478
+ var _a2;
5479
+ if (isOpen && focusedIndex >= 0 && optionsRefs.current[focusedIndex]) {
5480
+ (_a2 = optionsRefs.current[focusedIndex]) == null ? void 0 : _a2.scrollIntoView({
5481
+ block: "nearest",
5482
+ inline: "nearest"
5483
+ });
5484
+ }
5485
+ }, [focusedIndex, isOpen]);
5426
5486
  const handleSearchChange = (e) => {
5427
5487
  const newValue = e.target.value;
5428
5488
  if (isSearchControlled) {
@@ -5540,11 +5600,11 @@ const SelectDropdown = ({
5540
5600
  return "";
5541
5601
  };
5542
5602
  const handleTriggerClick = () => {
5543
- var _a;
5603
+ var _a2;
5544
5604
  if (!isOpen && !disabled) {
5545
5605
  setIsOpen(true);
5546
5606
  }
5547
- const input = (_a = containerRef.current) == null ? void 0 : _a.querySelector("input");
5607
+ const input = (_a2 = containerRef.current) == null ? void 0 : _a2.querySelector("input");
5548
5608
  if (input) {
5549
5609
  input.focus();
5550
5610
  }
@@ -5553,9 +5613,62 @@ const SelectDropdown = ({
5553
5613
  if (!isOpen) setIsOpen(true);
5554
5614
  handleSearchChange(e);
5555
5615
  };
5616
+ const handleOptionSelect = (option) => {
5617
+ var _a2;
5618
+ if (disabled || option.disabled) return;
5619
+ if (multiple) {
5620
+ const prev = Array.isArray(selected) ? [...selected] : [];
5621
+ const newSelected = prev.includes(option.value) ? prev.filter((v) => v !== option.value) : [...prev, option.value];
5622
+ if (prev.includes(option.value)) {
5623
+ selectedLabelsCacheRef.current.delete(option.value);
5624
+ } else {
5625
+ selectedLabelsCacheRef.current.set(option.value, option.label);
5626
+ }
5627
+ onSelect(newSelected);
5628
+ const input = (_a2 = containerRef.current) == null ? void 0 : _a2.querySelector("input");
5629
+ if (input) input.focus();
5630
+ } else {
5631
+ const isSelected = option.value === singleSelected;
5632
+ if (isSelected) {
5633
+ selectedLabelsCacheRef.current.delete(option.value);
5634
+ onSelect(null, option);
5635
+ } else {
5636
+ selectedLabelsCacheRef.current.set(option.value, option.label);
5637
+ onSelect(option.value, option);
5638
+ }
5639
+ setIsOpen(false);
5640
+ setFocusedIndex(-1);
5641
+ if (isSearchControlled) {
5642
+ onSearchChange == null ? void 0 : onSearchChange("");
5643
+ } else {
5644
+ setInternalSearchTerm("");
5645
+ }
5646
+ onClose == null ? void 0 : onClose();
5647
+ }
5648
+ };
5556
5649
  const handleInputKeyDown = (e) => {
5557
- if (e.key === "Enter") {
5650
+ if (e.key === "ArrowDown") {
5651
+ e.preventDefault();
5652
+ if (!isOpen) {
5653
+ setIsOpen(true);
5654
+ setFocusedIndex(0);
5655
+ } else {
5656
+ setFocusedIndex(
5657
+ (prev) => prev < filteredOptions.length - 1 ? prev + 1 : prev
5658
+ );
5659
+ }
5660
+ } else if (e.key === "ArrowUp") {
5661
+ e.preventDefault();
5662
+ setFocusedIndex((prev) => prev > 0 ? prev - 1 : prev);
5663
+ } else if (e.key === "Enter") {
5664
+ e.preventDefault();
5665
+ if (isOpen && focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
5666
+ handleOptionSelect(filteredOptions[focusedIndex]);
5667
+ }
5668
+ } else if (e.key === "Escape") {
5558
5669
  e.preventDefault();
5670
+ setIsOpen(false);
5671
+ setFocusedIndex(-1);
5559
5672
  }
5560
5673
  };
5561
5674
  return /* @__PURE__ */ jsxs(
@@ -5592,7 +5705,8 @@ const SelectDropdown = ({
5592
5705
  onChange: onInputChange,
5593
5706
  onKeyDown: handleInputKeyDown,
5594
5707
  disabled,
5595
- "aria-autocomplete": "list"
5708
+ "aria-autocomplete": "list",
5709
+ "aria-activedescendant": isOpen && focusedIndex >= 0 ? `option-${(_a = filteredOptions[focusedIndex]) == null ? void 0 : _a.value}` : void 0
5596
5710
  }
5597
5711
  ) : /* @__PURE__ */ jsx(
5598
5712
  "span",
@@ -5638,6 +5752,12 @@ const SelectDropdown = ({
5638
5752
  {
5639
5753
  ref: scrollContainerRef,
5640
5754
  className: "ina-select-dropdown__options",
5755
+ onScroll: (e) => {
5756
+ const target = e.currentTarget;
5757
+ if (target.scrollHeight - target.scrollTop - target.clientHeight < 50 && !loading && hasMore && onLoadMore && !disabled) {
5758
+ onLoadMore(currentPage + 1);
5759
+ }
5760
+ },
5641
5761
  children: [
5642
5762
  selectionTitle && /* @__PURE__ */ jsx("div", { className: "ina-select-dropdown__selection-title", children: selectionTitle }),
5643
5763
  filteredOptions.map((option, index) => {
@@ -5675,56 +5795,26 @@ const SelectDropdown = ({
5675
5795
  }
5676
5796
  );
5677
5797
  }
5678
- const handleOptionClick = (e) => {
5679
- var _a;
5680
- e.preventDefault();
5681
- e.stopPropagation();
5682
- if (disabled || option.disabled) return;
5683
- if (multiple) {
5684
- const prev = Array.isArray(selected) ? [...selected] : [];
5685
- const newSelected = prev.includes(option.value) ? prev.filter((v) => v !== option.value) : [...prev, option.value];
5686
- if (prev.includes(option.value)) {
5687
- selectedLabelsCacheRef.current.delete(option.value);
5688
- } else {
5689
- selectedLabelsCacheRef.current.set(
5690
- option.value,
5691
- option.label
5692
- );
5693
- }
5694
- onSelect(newSelected);
5695
- const input = (_a = containerRef.current) == null ? void 0 : _a.querySelector("input");
5696
- if (input) input.focus();
5697
- } else {
5698
- if (isSelected) {
5699
- selectedLabelsCacheRef.current.delete(option.value);
5700
- onSelect(null, option);
5701
- } else {
5702
- selectedLabelsCacheRef.current.set(
5703
- option.value,
5704
- option.label
5705
- );
5706
- onSelect(option.value, option);
5707
- }
5708
- setIsOpen(false);
5709
- if (isSearchControlled) {
5710
- onSearchChange == null ? void 0 : onSearchChange("");
5711
- } else {
5712
- setInternalSearchTerm("");
5713
- }
5714
- onClose == null ? void 0 : onClose();
5715
- }
5716
- };
5717
5798
  return /* @__PURE__ */ jsxs(
5718
5799
  "button",
5719
5800
  {
5801
+ ref: (el) => {
5802
+ optionsRefs.current[index] = el;
5803
+ },
5804
+ id: `option-${option.value}`,
5720
5805
  type: "button",
5721
- onClick: handleOptionClick,
5806
+ onClick: (e) => {
5807
+ e.preventDefault();
5808
+ e.stopPropagation();
5809
+ handleOptionSelect(option);
5810
+ },
5722
5811
  disabled: disabled || option.disabled,
5723
5812
  className: clsx(
5724
5813
  "ina-select-dropdown__option",
5725
5814
  !multiple && isSelected && "ina-select-dropdown__option--selected-single",
5726
5815
  multiple && isSelected && "ina-select-dropdown__option--selected-multiple",
5727
- (disabled || option.disabled) && "ina-select-dropdown__option--disabled"
5816
+ (disabled || option.disabled) && "ina-select-dropdown__option--disabled",
5817
+ index === focusedIndex && "ina-select-dropdown__option--focused"
5728
5818
  ),
5729
5819
  children: [
5730
5820
  /* @__PURE__ */ jsx("div", { className: "ina-select-dropdown__option-content", children: labelContent }),
@@ -5735,15 +5825,6 @@ const SelectDropdown = ({
5735
5825
  );
5736
5826
  }),
5737
5827
  filteredOptions.length === 0 && !loading && /* @__PURE__ */ jsx("div", { className: "ina-select-dropdown__empty", children: "No results" }),
5738
- hasMore && !loading && filteredOptions.length > 0 && onLoadMore && !disabled && /* @__PURE__ */ jsx("div", { className: "ina-select-dropdown__load-more", children: /* @__PURE__ */ jsx(
5739
- "button",
5740
- {
5741
- type: "button",
5742
- onClick: handleLoadMoreClick,
5743
- className: "ina-select-dropdown__load-more-button",
5744
- children: "Tampilkan Lebih Banyak"
5745
- }
5746
- ) }),
5747
5828
  loading && /* @__PURE__ */ jsxs("div", { className: "ina-select-dropdown__loading", children: [
5748
5829
  /* @__PURE__ */ jsx("div", { className: "ina-select-dropdown__loading-spinner", children: /* @__PURE__ */ jsx(IconLoader2, { size: 20 }) }),
5749
5830
  /* @__PURE__ */ jsx("span", { className: "ina-select-dropdown__loading-text", children: "Loading..." })