@vuu-ui/vuu-ui-controls 2.1.19-beta.1 → 2.1.19-beta.2

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 (180) hide show
  1. package/package.json +12 -13
  2. package/src/calendar/Calendar.css.js +10 -0
  3. package/src/calendar/Calendar.js +70 -0
  4. package/src/calendar/index.js +4 -0
  5. package/src/calendar/internal/CalendarCarousel.css.js +19 -0
  6. package/src/calendar/internal/CalendarCarousel.js +73 -0
  7. package/src/calendar/internal/CalendarContext.js +9 -0
  8. package/src/calendar/internal/CalendarDay.css.js +116 -0
  9. package/src/calendar/internal/CalendarDay.js +64 -0
  10. package/src/calendar/internal/CalendarMonth.css.js +13 -0
  11. package/src/calendar/internal/CalendarMonth.js +43 -0
  12. package/src/calendar/internal/CalendarNavigation.css.js +24 -0
  13. package/src/calendar/internal/CalendarNavigation.js +185 -0
  14. package/src/calendar/internal/CalendarWeekHeader.css.js +23 -0
  15. package/src/calendar/internal/CalendarWeekHeader.js +31 -0
  16. package/src/calendar/internal/useFocusManagement.js +66 -0
  17. package/src/calendar/internal/utils.js +57 -0
  18. package/src/calendar/useCalendar.js +116 -0
  19. package/src/calendar/useCalendarDay.js +74 -0
  20. package/src/calendar/useSelection.js +202 -0
  21. package/src/column-picker/ColumnPicker.css.js +8 -0
  22. package/src/column-picker/ColumnPicker.js +108 -0
  23. package/src/column-picker/ColumnSearch.css.js +61 -0
  24. package/src/column-picker/ColumnSearch.js +66 -0
  25. package/src/column-picker/index.js +2 -0
  26. package/src/column-picker/moving-window.js +49 -0
  27. package/src/common-hooks/index.js +3 -0
  28. package/src/common-hooks/selectionTypes.js +8 -0
  29. package/src/common-hooks/use-resize-observer.js +122 -0
  30. package/src/common-hooks/useControlled.js +45 -0
  31. package/src/context-panel-provider/ContextPanelProvider.js +80 -0
  32. package/src/cycle-state-button/CycleStateButton.js +30 -0
  33. package/src/cycle-state-button/index.js +1 -0
  34. package/src/date-input/DateInput.css.js +195 -0
  35. package/src/date-input/DateInput.js +210 -0
  36. package/src/date-input/index.js +1 -0
  37. package/src/date-picker/DatePicker.js +137 -0
  38. package/src/date-picker/DatePickerContext.js +20 -0
  39. package/src/date-picker/DatePickerPanel.css.js +25 -0
  40. package/src/date-picker/DatePickerPanel.js +124 -0
  41. package/src/date-picker/index.js +1 -0
  42. package/src/drag-drop/DragDropProvider.js +123 -0
  43. package/src/drag-drop/DragDropState.js +25 -0
  44. package/src/drag-drop/Draggable.css.js +57 -0
  45. package/src/drag-drop/Draggable.js +77 -0
  46. package/src/drag-drop/DropIndicator.css.js +11 -0
  47. package/src/drag-drop/DropIndicator.js +28 -0
  48. package/src/drag-drop/dragDropTypes.js +3 -0
  49. package/src/drag-drop/drop-target-utils.js +199 -0
  50. package/src/drag-drop/index.js +6 -0
  51. package/src/drag-drop/useAutoScroll.js +53 -0
  52. package/src/drag-drop/useDragDisplacers.js +126 -0
  53. package/src/drag-drop/useDragDrop.js +435 -0
  54. package/src/drag-drop/useDragDropCopy.js +22 -0
  55. package/src/drag-drop/useDragDropIndicator.js +182 -0
  56. package/src/drag-drop/useDragDropNaturalMovement.js +205 -0
  57. package/src/drag-drop/useDropIndicator.js +21 -0
  58. package/src/drag-drop/useGlobalDragDrop.js +67 -0
  59. package/src/drag-drop/useTransition.js +11 -0
  60. package/src/editable/index.js +1 -0
  61. package/src/editable/useEditableText.js +199 -0
  62. package/src/editable-label/EditableLabel.css.js +81 -0
  63. package/src/editable-label/EditableLabel.js +125 -0
  64. package/src/editable-label/index.js +1 -0
  65. package/src/expando-input/ExpandoInput.css.js +67 -0
  66. package/src/expando-input/ExpandoInput.js +40 -0
  67. package/src/expando-input/index.js +1 -0
  68. package/src/highlighter/Highlighter.css.js +8 -0
  69. package/src/highlighter/Highlighter.js +27 -0
  70. package/src/highlighter/index.js +1 -0
  71. package/src/hover-overlay/HoverOverlay.js +92 -0
  72. package/src/icon-button/Icon.css.js +8 -0
  73. package/src/icon-button/Icon.js +26 -0
  74. package/src/icon-button/IconButton.css.js +10 -0
  75. package/src/icon-button/IconButton.js +28 -0
  76. package/src/icon-button/ToggleIconButton.css.js +47 -0
  77. package/src/icon-button/ToggleIconButton.js +26 -0
  78. package/src/icon-button/index.js +3 -0
  79. package/src/index.js +31 -0
  80. package/src/instrument-picker/SearchCell.css.js +17 -0
  81. package/src/instrument-picker/SearchCell.js +29 -0
  82. package/src/instrument-picker/TablePicker.css.js +23 -0
  83. package/src/instrument-picker/TablePicker.js +89 -0
  84. package/src/instrument-picker/index.js +1 -0
  85. package/src/instrument-picker/moving-window.js +49 -0
  86. package/src/instrument-picker/useDataSource.js +38 -0
  87. package/src/instrument-picker/useTablePicker.js +136 -0
  88. package/src/measured-container/MeasuredContainer.css.js +11 -0
  89. package/src/measured-container/MeasuredContainer.js +50 -0
  90. package/src/measured-container/index.js +2 -0
  91. package/src/measured-container/useMeasuredContainer.js +156 -0
  92. package/src/modal-provider/ModalProvider.js +147 -0
  93. package/src/overflow-container/OverflowContainer.css.js +135 -0
  94. package/src/overflow-container/OverflowContainer.js +87 -0
  95. package/src/overflow-container/index.js +2 -0
  96. package/src/overflow-container/overflow-utils.js +211 -0
  97. package/src/overflow-container/useOverflowContainer.js +108 -0
  98. package/src/price-ticker/PriceTicker.css.js +32 -0
  99. package/src/price-ticker/PriceTicker.js +51 -0
  100. package/src/price-ticker/index.js +1 -0
  101. package/src/prompt/Prompt.css.js +48 -0
  102. package/src/prompt/Prompt.js +112 -0
  103. package/src/sortable-list/SortableList.js +39 -0
  104. package/src/split-button/SplitButton.css.js +117 -0
  105. package/src/split-button/SplitButton.js +60 -0
  106. package/src/split-button/SplitStateButton.css.js +66 -0
  107. package/src/split-button/SplitStateButton.js +23 -0
  108. package/src/split-button/index.js +2 -0
  109. package/src/split-button/useSplitButton.js +61 -0
  110. package/src/table-search/SearchCell.css.js +20 -0
  111. package/src/table-search/SearchCell.js +26 -0
  112. package/src/table-search/TableSearch.css.js +27 -0
  113. package/src/table-search/TableSearch.js +81 -0
  114. package/src/table-search/index.js +1 -0
  115. package/src/table-search/moving-window.js +49 -0
  116. package/src/table-search/useTableSearch.js +30 -0
  117. package/src/tabs-next/TabBar.css.js +26 -0
  118. package/src/tabs-next/TabBar.js +27 -0
  119. package/src/tabs-next/TabListNext.css.js +36 -0
  120. package/src/tabs-next/TabListNext.js +94 -0
  121. package/src/tabs-next/TabNext.css.js +76 -0
  122. package/src/tabs-next/TabNext.js +91 -0
  123. package/src/tabs-next/TabNextAction.js +26 -0
  124. package/src/tabs-next/TabNextContext.js +14 -0
  125. package/src/tabs-next/TabNextPanel.css.js +17 -0
  126. package/src/tabs-next/TabNextPanel.js +69 -0
  127. package/src/tabs-next/TabNextTrigger.css.js +22 -0
  128. package/src/tabs-next/TabNextTrigger.js +66 -0
  129. package/src/tabs-next/TabOverflowList.css.js +77 -0
  130. package/src/tabs-next/TabOverflowList.js +115 -0
  131. package/src/tabs-next/TabsNext.js +149 -0
  132. package/src/tabs-next/TabsNextContext.js +28 -0
  133. package/src/tabs-next/hooks/useCollection.js +80 -0
  134. package/src/tabs-next/hooks/useFocusOutside.js +23 -0
  135. package/src/tabs-next/hooks/useOverflow.js +118 -0
  136. package/src/tabs-next/index.js +7 -0
  137. package/src/tabstrip/Tab.css.js +148 -0
  138. package/src/tabstrip/Tab.js +107 -0
  139. package/src/tabstrip/TabMenu.css.js +8 -0
  140. package/src/tabstrip/TabMenu.js +48 -0
  141. package/src/tabstrip/TabMenuOptions.js +14 -0
  142. package/src/tabstrip/TabsTypes.js +0 -0
  143. package/src/tabstrip/Tabstrip.css.js +121 -0
  144. package/src/tabstrip/Tabstrip.js +106 -0
  145. package/src/tabstrip/ThemeTabstrip.css.js +20 -0
  146. package/src/tabstrip/index.js +4 -0
  147. package/src/tabstrip/tabstrip-dom-utils.js +11 -0
  148. package/src/tabstrip/useAnimatedSelectionThumb.js +68 -0
  149. package/src/tabstrip/useKeyboardNavigation.js +191 -0
  150. package/src/tabstrip/useSelection.js +50 -0
  151. package/src/tabstrip/useTabstrip.js +181 -0
  152. package/src/time-input/MaskedInput.js +285 -0
  153. package/src/time-input/TimeInput.css.js +39 -0
  154. package/src/time-input/TimeInput.js +37 -0
  155. package/src/time-input/useTimeInput.js +82 -0
  156. package/src/toolbar/Toolbar.css.js +46 -0
  157. package/src/toolbar/Toolbar.js +62 -0
  158. package/src/toolbar/index.js +1 -0
  159. package/src/toolbar/toolbar-dom-utils.js +11 -0
  160. package/src/toolbar/useKeyboardNavigation.js +240 -0
  161. package/src/toolbar/useSelection.js +82 -0
  162. package/src/toolbar/useToolbar.js +73 -0
  163. package/src/utils/deprecated-types.js +0 -0
  164. package/src/utils/escapeRegExp.js +5 -0
  165. package/src/utils/forwardCallbackProps.js +15 -0
  166. package/src/utils/index.js +5 -0
  167. package/src/utils/isOverflowElement.js +2 -0
  168. package/src/utils/isPlainObject.js +2 -0
  169. package/src/vuu-date-picker/VuuDatePicker.js +92 -0
  170. package/src/vuu-date-picker/index.js +1 -0
  171. package/src/vuu-input/VuuInput.css.js +13 -0
  172. package/src/vuu-input/VuuInput.js +85 -0
  173. package/src/vuu-input/index.js +1 -0
  174. package/src/vuu-time-picker/TimeRangePicker.js +0 -0
  175. package/src/vuu-time-picker/VuuTimePicker.css.js +14 -0
  176. package/src/vuu-time-picker/VuuTimePicker.js +26 -0
  177. package/src/vuu-typeahead-input/VuuTypeaheadInput.css.js +49 -0
  178. package/src/vuu-typeahead-input/VuuTypeaheadInput.js +46 -0
  179. package/src/vuu-typeahead-input/index.js +1 -0
  180. package/src/vuu-typeahead-input/useVuuTypeaheadInput.js +140 -0
@@ -0,0 +1,62 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { asReactElements, useId } from "@vuu-ui/vuu-utils";
3
+ import clsx from "clsx";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import react, { useMemo, useRef } from "react";
7
+ import { OverflowContainer } from "../overflow-container/index.js";
8
+ import { useToolbar } from "./useToolbar.js";
9
+ import { forwardCallbackProps } from "../utils/index.js";
10
+ import Toolbar from "./Toolbar.css";
11
+ const classBase = "vuuToolbar";
12
+ const Toolbar_Toolbar = ({ activeItemIndex: activeItemIndexProp, alignItems = "start", defaultActiveItemIndex, children, className, id: idProp, onActiveChange, onNavigateOutOfBounds, orientation = "horizontal", selectionStrategy = "none", showSeparators = false, ...props })=>{
13
+ const targetWindow = useWindow();
14
+ useComponentCssInjection({
15
+ testId: "vuu-toolbar",
16
+ css: Toolbar,
17
+ window: targetWindow
18
+ });
19
+ const rootRef = useRef(null);
20
+ const { activeItemIndex, focusableIdx, focusVisible, itemProps, ...toolbarHook } = useToolbar({
21
+ activeItemIndex: activeItemIndexProp,
22
+ defaultActiveItemIndex,
23
+ containerRef: rootRef,
24
+ onActiveChange,
25
+ onNavigateOutOfBounds,
26
+ orientation,
27
+ selectionStrategy
28
+ });
29
+ const id = useId(idProp);
30
+ const items = useMemo(()=>asReactElements(children).map((child, index)=>{
31
+ const { id: itemId = `${id}-tab-${index}`, className: itemClassName, ...ownProps } = child.props;
32
+ const selected = activeItemIndex.includes(index);
33
+ return /*#__PURE__*/ react.cloneElement(child, {
34
+ ...forwardCallbackProps(ownProps, itemProps),
35
+ className: clsx("vuuToolbarItem", itemClassName),
36
+ "data-overflow-priority": selected ? "1" : void 0,
37
+ id: itemId,
38
+ key: index,
39
+ "aria-selected": selected,
40
+ tabIndex: focusableIdx === index ? 0 : -1
41
+ });
42
+ }), [
43
+ activeItemIndex,
44
+ children,
45
+ focusableIdx,
46
+ id,
47
+ itemProps
48
+ ]);
49
+ return /*#__PURE__*/ jsx(OverflowContainer, {
50
+ ...props,
51
+ ...toolbarHook.containerProps,
52
+ className: clsx(className, classBase, `${classBase}-${orientation}`, {
53
+ [`${classBase}-alignCenter`]: "center" === alignItems,
54
+ [`${classBase}-alignEnd`]: "end" === alignItems,
55
+ [`${classBase}-withSeparators`]: showSeparators
56
+ }),
57
+ ...props,
58
+ ref: rootRef,
59
+ children: items
60
+ });
61
+ };
62
+ export { Toolbar_Toolbar as Toolbar };
@@ -0,0 +1 @@
1
+ export * from "./Toolbar.js";
@@ -0,0 +1,11 @@
1
+ import { getElementDataIndex } from "@vuu-ui/vuu-utils";
2
+ const getIndexOfItem = (container, query)=>{
3
+ if (container) {
4
+ const targetTab = container.querySelector(`[data-index]:has(${query})`);
5
+ return getElementDataIndex(targetTab);
6
+ }
7
+ return -1;
8
+ };
9
+ const getIndexOfSelectedTab = (container)=>getIndexOfItem(container, '[aria-selected="true"]');
10
+ const getIndexOfEditedItem = (container)=>getIndexOfItem(container, ".vuuEditableLabel-editing");
11
+ export { getIndexOfEditedItem, getIndexOfItem, getIndexOfSelectedTab };
@@ -0,0 +1,240 @@
1
+ import { useControlled } from "@salt-ds/core";
2
+ import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, End, Home, dispatchMouseEvent, getClosest, getElementDataIndex, getFocusableElement } from "@vuu-ui/vuu-utils";
3
+ import { useCallback, useRef, useState } from "react";
4
+ import { getIndexOfEditedItem } from "./toolbar-dom-utils.js";
5
+ const navigation = {
6
+ horizontal: {
7
+ [Home]: "start",
8
+ [End]: "end",
9
+ [ArrowLeft]: "bwd",
10
+ [ArrowRight]: "fwd"
11
+ },
12
+ vertical: {
13
+ [Home]: "start",
14
+ [End]: "end",
15
+ [ArrowUp]: "bwd",
16
+ [ArrowDown]: "fwd"
17
+ }
18
+ };
19
+ const isOverflowIndicator = (el)=>null !== el && "overflow" === el.dataset.index;
20
+ const itemIsNotFocusable = (container, direction, indexCount, nextIdx, hasOverflowedItem)=>{
21
+ if (!container) return false;
22
+ {
23
+ const withinRangeBwd = "bwd" === direction && nextIdx > 0;
24
+ const withinRangeFwd = "fwd" === direction && nextIdx < indexCount;
25
+ const withinRange = withinRangeBwd || withinRangeFwd;
26
+ const nextElement = getElementByPosition(container, nextIdx, true);
27
+ const isOverflowedItem = hasOverflowedItem && !isNonWrappedElement(nextElement);
28
+ const isHiddenOverflowIndicator = !hasOverflowedItem && isOverflowIndicator(nextElement);
29
+ hasOverflowedItem && isNonWrappedElement(nextElement);
30
+ return withinRange && (isOverflowedItem || isHiddenOverflowIndicator);
31
+ }
32
+ };
33
+ const isNavigationKey = (key, orientation = "horizontal")=>void 0 !== navigation[orientation][key];
34
+ const isMenuActivationKey = (key)=>key === ArrowDown;
35
+ function nextItemIdx(count, direction, idx) {
36
+ if ("start" === direction) return 0;
37
+ if ("end" === direction) return count - 1;
38
+ if ("bwd" === direction) if (idx > 0) return idx - 1;
39
+ else return idx;
40
+ if (null === idx) return 0;
41
+ if (idx === count - 1) return idx;
42
+ else return idx + 1;
43
+ }
44
+ const isNonWrappedElement = (element)=>null !== element && !element.classList.contains("wrapped");
45
+ const getToolbarItems = (container)=>Array.from(container.querySelectorAll("[data-index]"));
46
+ const getIndexOfOverflowItem = (container)=>{
47
+ if (null === container) return -1;
48
+ {
49
+ const targets = getToolbarItems(container);
50
+ const indexValues = targets.map((el)=>el.dataset.index);
51
+ return indexValues.indexOf("overflow");
52
+ }
53
+ };
54
+ const getElementByPosition = (container, index, includeOverflowInd = false)=>{
55
+ if (null !== container) {
56
+ const targets = getToolbarItems(container);
57
+ const target = targets[index];
58
+ if (!(!includeOverflowInd && isOverflowIndicator(target))) return target;
59
+ }
60
+ return null;
61
+ };
62
+ const useKeyboardNavigation = ({ containerRef, defaultHighlightedIdx = -1, highlightedIdx: highlightedIdxProp, onNavigateOutOfBounds, orientation })=>{
63
+ const mouseClickPending = useRef(false);
64
+ const focusedRef = useRef(-1);
65
+ const [hasFocus, setHasFocus] = useState(false);
66
+ const [highlightedIdx, _setHighlightedIdx] = useControlled({
67
+ controlled: highlightedIdxProp,
68
+ default: defaultHighlightedIdx,
69
+ name: "UseKeyboardNavigation"
70
+ });
71
+ const setHighlightedIdx = useCallback((value)=>{
72
+ _setHighlightedIdx(focusedRef.current = value);
73
+ }, [
74
+ _setHighlightedIdx
75
+ ]);
76
+ const keyboardNavigation = useRef(false);
77
+ const focusItem = useCallback((itemIndex, immediateFocus = false, withKeyboard, delay = 70)=>{
78
+ console.log(`focus item ${itemIndex}`);
79
+ setHighlightedIdx(itemIndex);
80
+ if (true === withKeyboard && !keyboardNavigation.current) keyboardNavigation.current = true;
81
+ const setFocus = ()=>{
82
+ const element = getElementByPosition(containerRef.current, itemIndex, true);
83
+ if (element) {
84
+ const focussableElement = getFocusableElement(element);
85
+ focussableElement?.focus();
86
+ }
87
+ };
88
+ if (immediateFocus) setFocus();
89
+ else setTimeout(setFocus, delay);
90
+ }, [
91
+ containerRef,
92
+ setHighlightedIdx
93
+ ]);
94
+ const onFocus = (e)=>{
95
+ if (-1 === focusedRef.current) if (0 === e.target.tabIndex) {
96
+ const index = getElementDataIndex(getClosest(e.target, "index"));
97
+ setHighlightedIdx(index);
98
+ } else if (-1 === e.target.tabIndex) ;
99
+ else {
100
+ const index = getIndexOfEditedItem(containerRef.current);
101
+ if (-1 !== index) requestAnimationFrame(()=>{
102
+ setHighlightedIdx(index);
103
+ });
104
+ }
105
+ };
106
+ const getIndexCount = useCallback(()=>containerRef.current?.querySelectorAll("[data-index]").length ?? 0, [
107
+ containerRef
108
+ ]);
109
+ const nextFocusableItemIdx = useCallback((direction = "fwd", idx)=>{
110
+ const indexCount = getIndexCount();
111
+ const index = "number" == typeof idx ? idx : indexCount;
112
+ let nextIdx = nextItemIdx(indexCount, direction, index);
113
+ const nextDirection = "start" === direction ? "fwd" : "end" === direction ? "bwd" : direction;
114
+ const hasOverflowedItem = containerRef.current?.querySelector(".vuuOverflowContainer-wrapContainer-overflowed") != null;
115
+ while(itemIsNotFocusable(containerRef.current, nextDirection, indexCount, nextIdx, hasOverflowedItem)){
116
+ const newIdx = nextItemIdx(indexCount, nextDirection, nextIdx);
117
+ if (newIdx === nextIdx) return index;
118
+ nextIdx = newIdx;
119
+ }
120
+ return nextIdx;
121
+ }, [
122
+ containerRef,
123
+ getIndexCount
124
+ ]);
125
+ const navigateChildItems = useCallback((e)=>{
126
+ const direction = navigation[orientation][e.key];
127
+ const nextIdx = nextFocusableItemIdx(direction, highlightedIdx);
128
+ console.log(`highlightedIdx = ${highlightedIdx}, nextIdx = ${nextIdx} `);
129
+ if (nextIdx !== highlightedIdx) {
130
+ const immediateFocus = true;
131
+ focusItem(nextIdx, immediateFocus);
132
+ } else onNavigateOutOfBounds?.("bwd" === direction ? "start" : "end");
133
+ }, [
134
+ orientation,
135
+ nextFocusableItemIdx,
136
+ highlightedIdx,
137
+ focusItem,
138
+ onNavigateOutOfBounds
139
+ ]);
140
+ const highlightedItemHasMenu = useCallback(()=>{
141
+ const el = getElementByPosition(containerRef.current, highlightedIdx);
142
+ if (el) return null != el.querySelector(".vuuPopupMenu");
143
+ return false;
144
+ }, [
145
+ containerRef,
146
+ highlightedIdx
147
+ ]);
148
+ const highlightedItemInEditState = useCallback(()=>{
149
+ const el = getElementByPosition(containerRef.current, highlightedIdx);
150
+ if (el) return null != el.querySelector(".vuuEditableLabel-input");
151
+ return false;
152
+ }, [
153
+ containerRef,
154
+ highlightedIdx
155
+ ]);
156
+ const activateItemMenu = useCallback(()=>{
157
+ const el = getElementByPosition(containerRef.current, highlightedIdx);
158
+ const menuEl = el?.querySelector(".vuuPopupMenu");
159
+ if (menuEl) dispatchMouseEvent(menuEl, "click");
160
+ return false;
161
+ }, [
162
+ containerRef,
163
+ highlightedIdx
164
+ ]);
165
+ const handleKeyDown = useCallback((e)=>{
166
+ if (getIndexCount() > 0 && isNavigationKey(e.key, orientation)) {
167
+ e.preventDefault();
168
+ if (keyboardNavigation.current) navigateChildItems(e);
169
+ else {
170
+ keyboardNavigation.current = true;
171
+ navigateChildItems(e);
172
+ }
173
+ } else if (isMenuActivationKey(e.key) && highlightedItemHasMenu() && !highlightedItemInEditState()) activateItemMenu();
174
+ }, [
175
+ activateItemMenu,
176
+ getIndexCount,
177
+ highlightedItemHasMenu,
178
+ highlightedItemInEditState,
179
+ navigateChildItems,
180
+ orientation
181
+ ]);
182
+ const handleItemClick = (_, itemIndex)=>{
183
+ setHighlightedIdx(itemIndex);
184
+ };
185
+ const handleFocus = useCallback(()=>{
186
+ if (!hasFocus) {
187
+ setHasFocus(true);
188
+ if (mouseClickPending.current) mouseClickPending.current = false;
189
+ else keyboardNavigation.current = true;
190
+ }
191
+ }, [
192
+ hasFocus
193
+ ]);
194
+ const handleContainerMouseDown = useCallback(()=>{
195
+ if (!hasFocus) mouseClickPending.current = true;
196
+ keyboardNavigation.current = false;
197
+ }, [
198
+ hasFocus
199
+ ]);
200
+ const handleOverflowMenuClose = useCallback((closeReason)=>{
201
+ if (closeReason?.type === "escape") {
202
+ const index = getIndexOfOverflowItem(containerRef.current);
203
+ if (-1 !== index) focusItem(index);
204
+ }
205
+ }, [
206
+ containerRef,
207
+ focusItem
208
+ ]);
209
+ const containerProps = {
210
+ onBlur: (e)=>{
211
+ const sourceTarget = e.target.closest(".vuuToolbar");
212
+ const destTarget = e.relatedTarget;
213
+ if (sourceTarget && !sourceTarget?.contains(destTarget)) {
214
+ setHighlightedIdx(-1);
215
+ setHasFocus(false);
216
+ }
217
+ },
218
+ onMouseDownCapture: handleContainerMouseDown,
219
+ onFocus: handleFocus,
220
+ onMouseLeave: ()=>{
221
+ keyboardNavigation.current = true;
222
+ setHighlightedIdx(-1);
223
+ mouseClickPending.current = false;
224
+ }
225
+ };
226
+ return {
227
+ containerProps,
228
+ focusVisible: keyboardNavigation.current ? highlightedIdx : -1,
229
+ focusIsWithinComponent: hasFocus,
230
+ highlightedIdx,
231
+ focusableIdx: 0,
232
+ focusItem,
233
+ onClick: handleItemClick,
234
+ onFocus,
235
+ onKeyDown: handleKeyDown,
236
+ onOverflowMenuClose: handleOverflowMenuClose,
237
+ setHighlightedIdx
238
+ };
239
+ };
240
+ export { useKeyboardNavigation };
@@ -0,0 +1,82 @@
1
+ import { useCallback } from "react";
2
+ import { allowMultipleSelection, deselectionIsAllowed, selectionIsDisallowed } from "../common-hooks/index.js";
3
+ import { useControlled } from "@salt-ds/core";
4
+ import { getClosestIndexItem, isSelectableElement } from "@vuu-ui/vuu-utils";
5
+ const defaultSelectionKeys = [
6
+ "Enter",
7
+ " "
8
+ ];
9
+ const NO_SELECTION_HANDLERS = {};
10
+ const useSelection = ({ defaultSelected, highlightedIdx, onSelectionChange, selected: selectedProp, selectionStrategy })=>{
11
+ const [selected, setSelected, isControlled] = useControlled({
12
+ controlled: selectedProp,
13
+ default: defaultSelected ?? [],
14
+ name: "useSelection",
15
+ state: "selected"
16
+ });
17
+ const isSelectionEvent = useCallback((evt)=>defaultSelectionKeys.includes(evt.key), []);
18
+ const selectItem = useCallback((itemIndex, specialKey = false)=>{
19
+ const newSelected = allowMultipleSelection(selectionStrategy, specialKey) ? selected.concat(itemIndex) : [
20
+ itemIndex
21
+ ];
22
+ setSelected(newSelected);
23
+ onSelectionChange?.(newSelected);
24
+ }, [
25
+ onSelectionChange,
26
+ selected,
27
+ selectionStrategy,
28
+ setSelected
29
+ ]);
30
+ const deselectItem = useCallback((itemIndex, specialKey = false)=>{
31
+ const newSelected = "deselectable" !== selectionStrategy && ("multiple-special-key" !== selectionStrategy || specialKey) ? selected.filter((index)=>index !== itemIndex) : [];
32
+ setSelected(newSelected);
33
+ onSelectionChange?.(newSelected);
34
+ }, [
35
+ onSelectionChange,
36
+ selected,
37
+ selectionStrategy,
38
+ setSelected
39
+ ]);
40
+ const handleKeyDown = useCallback((e)=>{
41
+ if (isSelectionEvent(e) && isSelectableElement(e.target)) if (selected.includes(highlightedIdx)) {
42
+ if (deselectionIsAllowed(selectionStrategy)) {
43
+ e.stopPropagation();
44
+ e.preventDefault();
45
+ deselectItem(highlightedIdx, e.shiftKey);
46
+ }
47
+ } else {
48
+ e.stopPropagation();
49
+ e.preventDefault();
50
+ selectItem(highlightedIdx, e.shiftKey);
51
+ }
52
+ }, [
53
+ isSelectionEvent,
54
+ selected,
55
+ highlightedIdx,
56
+ selectionStrategy,
57
+ selectItem,
58
+ deselectItem
59
+ ]);
60
+ const handleClick = useCallback((e, itemIndex)=>{
61
+ const element = getClosestIndexItem(e.target);
62
+ if (isSelectableElement(element)) if (selected.includes(itemIndex)) {
63
+ if (deselectionIsAllowed(selectionStrategy)) deselectItem(itemIndex, e.shiftKey);
64
+ } else selectItem(itemIndex, e.shiftKey);
65
+ }, [
66
+ deselectItem,
67
+ selectItem,
68
+ selected,
69
+ selectionStrategy
70
+ ]);
71
+ const itemHandlers = selectionIsDisallowed(selectionStrategy) ? NO_SELECTION_HANDLERS : {
72
+ onClick: handleClick,
73
+ onKeyDown: handleKeyDown
74
+ };
75
+ return {
76
+ activateItem: selectItem,
77
+ itemHandlers,
78
+ isControlled,
79
+ selected
80
+ };
81
+ };
82
+ export { useSelection };
@@ -0,0 +1,73 @@
1
+ import { isValidNumber } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useRef } from "react";
3
+ import { useKeyboardNavigation } from "./useKeyboardNavigation.js";
4
+ import { useSelection } from "./useSelection.js";
5
+ const useToolbar = ({ activeItemIndex: activeItemIndexProp, defaultActiveItemIndex, containerRef, itemQuery = "vuuToolbarItem", onActiveChange, onNavigateOutOfBounds, orientation, selectionStrategy })=>{
6
+ const lastSelection = useRef(activeItemIndexProp);
7
+ const { focusItem: keyboardHookFocusItem, highlightedIdx, onClick: keyboardHookHandleClick, onKeyDown: keyboardHookHandleKeyDown, setHighlightedIdx: keyboardHookSetHighlightedIndex, ...keyboardHook } = useKeyboardNavigation({
8
+ containerRef,
9
+ onNavigateOutOfBounds,
10
+ orientation
11
+ });
12
+ const { activateItem: selectionHookActivateItem, itemHandlers: { onClick, onKeyDown }, selected: selectionHookSelected } = useSelection({
13
+ containerRef,
14
+ defaultSelected: defaultActiveItemIndex,
15
+ highlightedIdx,
16
+ itemQuery,
17
+ onSelectionChange: onActiveChange,
18
+ selected: activeItemIndexProp,
19
+ selectionStrategy
20
+ });
21
+ lastSelection.current = selectionHookSelected;
22
+ const handleClick = useCallback((evt)=>{
23
+ const target = evt.target;
24
+ const toolbarItem = target.closest("[data-index]");
25
+ if (toolbarItem) {
26
+ const index = parseInt(toolbarItem.dataset.index ?? "-1");
27
+ if (-1 !== index && isValidNumber(index)) {
28
+ keyboardHookHandleClick(evt, index);
29
+ onClick?.(evt, index);
30
+ }
31
+ }
32
+ }, [
33
+ keyboardHookHandleClick,
34
+ onClick
35
+ ]);
36
+ const handleKeyDown = useCallback((evt)=>{
37
+ keyboardHookHandleKeyDown(evt);
38
+ if (!evt.defaultPrevented) onKeyDown?.(evt);
39
+ }, [
40
+ keyboardHookHandleKeyDown,
41
+ onKeyDown
42
+ ]);
43
+ const onSwitchWrappedItemIntoView = useCallback((item)=>{
44
+ const index = parseInt(item.index);
45
+ if (!isNaN(index)) {
46
+ selectionHookActivateItem(index);
47
+ keyboardHookFocusItem(index);
48
+ }
49
+ }, [
50
+ keyboardHookFocusItem,
51
+ selectionHookActivateItem
52
+ ]);
53
+ const itemProps = {
54
+ onClick: handleClick,
55
+ onFocus: keyboardHook.onFocus,
56
+ onKeyDown: handleKeyDown
57
+ };
58
+ return {
59
+ activeItemIndex: selectionHookSelected,
60
+ focusableIdx: keyboardHook.focusableIdx,
61
+ focusVisible: keyboardHook.focusVisible,
62
+ containerProps: {
63
+ PopupMenuProps: {
64
+ onKeyDown: handleKeyDown,
65
+ onMenuClose: keyboardHook.onOverflowMenuClose
66
+ },
67
+ ...keyboardHook.containerProps,
68
+ onSwitchWrappedItemIntoView
69
+ },
70
+ itemProps
71
+ };
72
+ };
73
+ export { useToolbar };
File without changes
@@ -0,0 +1,5 @@
1
+ const regExp = /[.*+?^${}()|[\]\\]/g;
2
+ function escapeRegExp(string) {
3
+ return string.replace(regExp, "\\$&");
4
+ }
5
+ export { escapeRegExp };
@@ -0,0 +1,15 @@
1
+ const forwardCallbackProps = (ownProps, overrideProps)=>{
2
+ const props = Object.keys(ownProps).reduce((map, name)=>{
3
+ const ownProp = ownProps[name];
4
+ const overrideProp = overrideProps[name];
5
+ if ("function" == typeof ownProp && "function" == typeof overrideProp) map[name] = (...args)=>{
6
+ ownProp(...args);
7
+ overrideProp(...args);
8
+ };
9
+ return map;
10
+ }, {
11
+ ...overrideProps
12
+ });
13
+ return props;
14
+ };
15
+ export { forwardCallbackProps };
@@ -0,0 +1,5 @@
1
+ export * from "./escapeRegExp.js";
2
+ export * from "./isOverflowElement.js";
3
+ export * from "./isPlainObject.js";
4
+ export * from "./forwardCallbackProps.js";
5
+ export * from "./deprecated-types.js";
@@ -0,0 +1,2 @@
1
+ const isOverflowElement = (element)=>null !== element && "overflow" === element.dataset.index && null !== element.parentElement && element.parentElement.classList.contains("vuuOverflowContainer-wrapContainer-overflowed");
2
+ export { isOverflowElement };
@@ -0,0 +1,2 @@
1
+ const isPlainObject = (obj)=>"[object Object]" === Object.prototype.toString.call(obj);
2
+ export { isPlainObject };
@@ -0,0 +1,92 @@
1
+ import { createElement, useCallback, useRef, useState } from "react";
2
+ import { getLocalTimeZone, isSameDay, isSameMonth, isSameYear } from "@internationalized/date";
3
+ import clsx from "clsx";
4
+ import { DatePicker } from "../date-picker/index.js";
5
+ const classBase = "VuuDatePicker";
6
+ const isSameDate = (d1, d2)=>void 0 !== d2 && isSameDay(d1, d2) && isSameMonth(d1, d2) && isSameYear(d1, d2);
7
+ const datePattern = /^\d{1,2} [a-z]{3} \d{4}$/i;
8
+ const isValidDate = (value)=>void 0 !== value && value?.match(datePattern) !== null && !Number.isNaN(new Date(value).getDay());
9
+ const localTimeZone = getLocalTimeZone();
10
+ const toEpochMillis = (date, timezone = getLocalTimeZone())=>date.toDate(timezone).getTime();
11
+ const getDates = (selectedDate)=>{
12
+ if (selectedDate) return {
13
+ datePickerKey: "controlled",
14
+ defaultDate: void 0,
15
+ selectedDate
16
+ };
17
+ return {
18
+ datePickerKey: "uncontrolled",
19
+ defaultDate: void 0,
20
+ selectedDate
21
+ };
22
+ };
23
+ const VuuDatePicker = ({ className, onSelectionChange, selectedDate: selectedDateProp, onCommit, preserveFocusOnSelect, ...props })=>{
24
+ const [open, _setOpen] = useState(false);
25
+ const valueRef = useRef("");
26
+ const datePickerRef = useRef(null);
27
+ const dateState = useRef(getDates(selectedDateProp));
28
+ const setOpen = (o)=>{
29
+ console.log(`setOpen ${o}`);
30
+ _setOpen(o);
31
+ };
32
+ const commitDateChange = useCallback((e, date)=>{
33
+ onSelectionChange?.(e, date);
34
+ setOpen(false);
35
+ onCommit?.(e, toEpochMillis(date, localTimeZone));
36
+ if (preserveFocusOnSelect) requestAnimationFrame(()=>{
37
+ datePickerRef.current?.querySelector("input")?.focus();
38
+ });
39
+ }, [
40
+ onCommit,
41
+ onSelectionChange,
42
+ preserveFocusOnSelect
43
+ ]);
44
+ const handleSelectionChange = useCallback((e, date)=>{
45
+ const { selectedDate } = dateState.current;
46
+ if (date) {
47
+ if (void 0 === selectedDate) {
48
+ dateState.current = getDates(date);
49
+ commitDateChange(e, date);
50
+ } else if (!isSameDate(date, selectedDate)) {
51
+ dateState.current.selectedDate = date;
52
+ commitDateChange(e, date);
53
+ }
54
+ }
55
+ }, [
56
+ commitDateChange
57
+ ]);
58
+ const handleChange = (evt, value = "")=>{
59
+ console.log("handleChange", {
60
+ evt,
61
+ value
62
+ });
63
+ valueRef.current = value;
64
+ console.log(`is '${value}' a valid date ? ${isValidDate(value)}`);
65
+ };
66
+ const handleKeyDown = (e)=>{
67
+ if ("ArrowDown" === e.key) setOpen(true);
68
+ };
69
+ const handleKeyDownCapture = (e)=>{
70
+ if ("Enter" === e.key) if (isValidDate(valueRef.current)) console.log(`committayaface (${valueRef.current})`);
71
+ else {
72
+ console.log(`nice try cowboy, but '${valueRef.current}' is nota valid date`);
73
+ e.stopPropagation();
74
+ }
75
+ };
76
+ const { datePickerKey, defaultDate, selectedDate } = dateState.current;
77
+ return /*#__PURE__*/ createElement(DatePicker, {
78
+ ...props,
79
+ className: clsx(classBase, className),
80
+ defaultSelectedDate: defaultDate,
81
+ key: datePickerKey,
82
+ open: open,
83
+ onChange: handleChange,
84
+ onKeyDown: handleKeyDown,
85
+ onKeyDownCapture: handleKeyDownCapture,
86
+ onOpenChange: setOpen,
87
+ ref: datePickerRef,
88
+ onSelectionChange: (e, date)=>handleSelectionChange(e, date),
89
+ selectedDate: selectedDate
90
+ });
91
+ };
92
+ export { VuuDatePicker };
@@ -0,0 +1 @@
1
+ export * from "./VuuDatePicker.js";
@@ -0,0 +1,13 @@
1
+ const css = `
2
+ .vuuInput {
3
+ --vuu-icon-size: 16px;
4
+ }
5
+
6
+ .vuuInput-errorIcon:after {
7
+ --vuu-icon-color: var(--salt-status-error-background);
8
+ cursor: pointer;
9
+ }
10
+
11
+
12
+ `;
13
+ export default css;