@duskmoon-dev/components 0.5.2 → 0.5.4

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.
@@ -1,7 +1,15 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/select/Select.tsx
4
- import React, { forwardRef, useMemo, useState } from "react";
4
+ import React, {
5
+ forwardRef,
6
+ useCallback,
7
+ useEffect,
8
+ useId,
9
+ useMemo,
10
+ useRef,
11
+ useState
12
+ } from "react";
5
13
 
6
14
  // ../../node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
7
15
  function r(e) {
@@ -180,6 +188,7 @@ var SelectRoot = forwardRef(({
180
188
  "aria-required": ariaRequired,
181
189
  children,
182
190
  className,
191
+ defaultOpen = false,
183
192
  defaultValue,
184
193
  disabled,
185
194
  filterOption,
@@ -187,6 +196,9 @@ var SelectRoot = forwardRef(({
187
196
  loading,
188
197
  mode,
189
198
  onChange,
199
+ onDropdownVisibleChange,
200
+ onOpenChange,
201
+ open: openProp,
190
202
  options,
191
203
  placeholder,
192
204
  showSearch,
@@ -195,10 +207,60 @@ var SelectRoot = forwardRef(({
195
207
  value,
196
208
  ...props
197
209
  }, ref) => {
210
+ const generatedId = useId();
211
+ const cleanId = generatedId.replace(/[^a-zA-Z0-9_-]/g, "");
212
+ const triggerId = id || `select-${cleanId}`;
213
+ const dropdownId = `select-dropdown-${cleanId}`;
214
+ const anchorName = `--dm-select-${cleanId}`;
215
+ const [internalOpen, setInternalOpen] = useState(Boolean(defaultOpen));
216
+ const isControlled = openProp !== undefined;
217
+ const isOpen = isControlled ? Boolean(openProp) : internalOpen;
218
+ const containerRef = useRef(null);
219
+ const dropdownRef = useRef(null);
220
+ const setOpenState = useCallback((nextOpen) => {
221
+ if (!isControlled) {
222
+ setInternalOpen(nextOpen);
223
+ }
224
+ onDropdownVisibleChange?.(nextOpen);
225
+ onOpenChange?.(nextOpen);
226
+ }, [isControlled, onDropdownVisibleChange, onOpenChange]);
227
+ useEffect(() => {
228
+ const dropdown = dropdownRef.current;
229
+ if (!dropdown || typeof dropdown.showPopover !== "function")
230
+ return;
231
+ if (isOpen && !dropdown.matches(":popover-open")) {
232
+ try {
233
+ dropdown.showPopover();
234
+ } catch {}
235
+ } else if (!isOpen && dropdown.matches(":popover-open")) {
236
+ try {
237
+ dropdown.hidePopover();
238
+ } catch {}
239
+ }
240
+ }, [isOpen]);
241
+ useEffect(() => {
242
+ if (!isOpen)
243
+ return;
244
+ const handlePointerDown = (event) => {
245
+ const target = event.target;
246
+ if (!target)
247
+ return;
248
+ if (containerRef.current?.contains(target))
249
+ return;
250
+ if (dropdownRef.current?.contains(target))
251
+ return;
252
+ setOpenState(false);
253
+ };
254
+ document.addEventListener("mousedown", handlePointerDown);
255
+ document.addEventListener("touchstart", handlePointerDown);
256
+ return () => {
257
+ document.removeEventListener("mousedown", handlePointerDown);
258
+ document.removeEventListener("touchstart", handlePointerDown);
259
+ };
260
+ }, [isOpen, setOpenState]);
198
261
  const multiple = mode === "multiple" || mode === "tags";
199
262
  const sourceOptions = useMemo(() => options ?? optionsFromChildren(children), [children, options]);
200
263
  const flatOptions = useMemo(() => flattenOptions(sourceOptions), [sourceOptions]);
201
- const [open, setOpen] = useState(false);
202
264
  const [searchValue, setSearchValue] = useState("");
203
265
  const [internalValue, setInternalValue] = useState(multiple ? normalizeMultiValue(defaultValue) : normalizeSingleValue(defaultValue));
204
266
  const controlled = value !== undefined;
@@ -233,7 +295,7 @@ var SelectRoot = forwardRef(({
233
295
  emitChange(nextValues);
234
296
  } else {
235
297
  emitChange(option.value);
236
- setOpen(false);
298
+ setOpenState(false);
237
299
  }
238
300
  setSearchValue("");
239
301
  }
@@ -289,15 +351,23 @@ var SelectRoot = forwardRef(({
289
351
  }, `group-${index}`);
290
352
  });
291
353
  }
354
+ const setRefs = (node) => {
355
+ containerRef.current = node;
356
+ if (typeof ref === "function") {
357
+ ref(node);
358
+ } else if (ref) {
359
+ ref.current = node;
360
+ }
361
+ };
292
362
  const selection = multiple ? selectedOptions.map((option) => /* @__PURE__ */ jsx("span", {
293
363
  className: selectTagClass,
294
364
  children: renderValue(option)
295
365
  }, valueKey(option.value))) : renderValue(selectedOptions[0]);
296
366
  return /* @__PURE__ */ jsxs("div", {
297
367
  ...props,
298
- ref,
368
+ ref: setRefs,
299
369
  className: getSelectContainerClasses({
300
- open,
370
+ open: isOpen,
301
371
  loading,
302
372
  disabled,
303
373
  full: true,
@@ -306,7 +376,7 @@ var SelectRoot = forwardRef(({
306
376
  children: [
307
377
  /* @__PURE__ */ jsxs("button", {
308
378
  type: "button",
309
- id,
379
+ id: triggerId,
310
380
  className: getSelectClasses({ size, status, disabled }),
311
381
  disabled: disabled || loading,
312
382
  "aria-describedby": ariaDescribedBy,
@@ -316,8 +386,14 @@ var SelectRoot = forwardRef(({
316
386
  "aria-labelledby": ariaLabelledBy,
317
387
  "aria-required": ariaRequired,
318
388
  "aria-haspopup": "listbox",
319
- "aria-expanded": open,
320
- onClick: () => setOpen((current) => !current),
389
+ "aria-expanded": isOpen,
390
+ "aria-controls": dropdownId,
391
+ style: { anchorName },
392
+ onClick: () => {
393
+ if (disabled || loading)
394
+ return;
395
+ setOpenState(!isOpen);
396
+ },
321
397
  children: [
322
398
  /* @__PURE__ */ jsx("span", {
323
399
  className: selectSelectionClass,
@@ -365,10 +441,22 @@ var SelectRoot = forwardRef(({
365
441
  })
366
442
  })
367
443
  }) : null,
368
- open ? /* @__PURE__ */ jsxs("div", {
369
- className: selectDropdownClass,
444
+ isOpen ? /* @__PURE__ */ jsxs("div", {
445
+ id: dropdownId,
446
+ ref: dropdownRef,
447
+ popover: "auto",
370
448
  role: "listbox",
371
449
  "aria-multiselectable": multiple || undefined,
450
+ className: selectDropdownClass,
451
+ style: { positionAnchor: anchorName },
452
+ onToggle: (event) => {
453
+ if (event.target !== event.currentTarget)
454
+ return;
455
+ const nextOpen = event.nativeEvent?.newState === "open";
456
+ if (nextOpen !== isOpen) {
457
+ setOpenState(nextOpen);
458
+ }
459
+ },
372
460
  children: [
373
461
  searchable ? /* @__PURE__ */ jsx("input", {
374
462
  className: selectSearchClass,
@@ -6,7 +6,7 @@ export interface TooltipProps extends Omit<ComponentProps<"span">, "title"> {
6
6
  placement?: TooltipPlacement;
7
7
  open?: boolean;
8
8
  defaultOpen?: boolean;
9
- /** Called when hover, focus, or dismissal requests a visibility change.
9
+ /** Called when native interest changes visibility, or a controlled interaction requests it.
10
10
  * Controlled tooltips use manual popovers and stay open until `open` changes. */
11
11
  onOpenChange?: (open: boolean) => void;
12
12
  /** @deprecated Native tooltips follow Core's arrowless automatic placement. */
@@ -62,7 +62,7 @@ var Tooltip = forwardRef(({
62
62
  open,
63
63
  defaultOpen,
64
64
  onOpenChange,
65
- arrow = true,
65
+ arrow = false,
66
66
  size = "md",
67
67
  className,
68
68
  children,
@@ -90,12 +90,21 @@ var Tooltip = forwardRef(({
90
90
  controlledRef.current = isControlled;
91
91
  onOpenChangeRef.current = onOpenChange;
92
92
  const hasTitle = Boolean(title);
93
+ const nativeInterest = !isControlled && React.isValidElement(children) && (typeof children.type !== "string" || children.type === "button" || children.type === "a") && children.type !== React.Fragment;
93
94
  useEffect(() => {
94
95
  const surface = surfaceRef.current;
95
96
  if (!surface)
96
97
  return;
97
98
  const tooltipSurface = surface;
98
99
  function handleToggle(event) {
100
+ if (nativeInterest) {
101
+ const next = event.newState === "open";
102
+ if (next !== tooltipSurface.matches(":popover-open") || next === visibleRef.current)
103
+ return;
104
+ onOpenChangeRef.current?.(next);
105
+ setInternalOpen(next);
106
+ return;
107
+ }
99
108
  if (event.newState !== "closed" || !visibleRef.current || isControlled || isControlled !== controlledRef.current || tooltipSurface.matches(":popover-open")) {
100
109
  return;
101
110
  }
@@ -107,7 +116,14 @@ var Tooltip = forwardRef(({
107
116
  }
108
117
  surface.addEventListener("toggle", handleToggle);
109
118
  return () => surface.removeEventListener("toggle", handleToggle);
110
- }, [hasTitle, isControlled]);
119
+ }, [hasTitle, isControlled, nativeInterest]);
120
+ useEffect(() => {
121
+ const surface = surfaceRef.current;
122
+ return () => {
123
+ if (surface?.matches(":popover-open"))
124
+ surface.hidePopover();
125
+ };
126
+ }, [hasTitle, isControlled, nativeInterest]);
111
127
  useEffect(() => {
112
128
  const surface = surfaceRef.current;
113
129
  if (!surface)
@@ -157,21 +173,29 @@ var Tooltip = forwardRef(({
157
173
  }
158
174
  function showTooltip(event) {
159
175
  onMouseEnter?.(event);
176
+ if (nativeInterest)
177
+ return;
160
178
  hoveredRef.current = true;
161
179
  updateInteraction();
162
180
  }
163
181
  function hideTooltip(event) {
164
182
  onMouseLeave?.(event);
183
+ if (nativeInterest)
184
+ return;
165
185
  hoveredRef.current = false;
166
186
  updateInteraction();
167
187
  }
168
188
  function focusTooltip(event) {
169
189
  onFocus?.(event);
190
+ if (nativeInterest)
191
+ return;
170
192
  focusedRef.current = true;
171
193
  updateInteraction();
172
194
  }
173
195
  function blurTooltip(event) {
174
196
  onBlur?.(event);
197
+ if (nativeInterest)
198
+ return;
175
199
  if (event.currentTarget.contains(event.relatedTarget))
176
200
  return;
177
201
  focusedRef.current = false;
@@ -180,6 +204,13 @@ var Tooltip = forwardRef(({
180
204
  let trigger = children;
181
205
  if (hasTitle && React.isValidElement(children) && children.type !== React.Fragment) {
182
206
  trigger = React.cloneElement(children, {
207
+ ...nativeInterest ? {
208
+ interestfor: tooltipId,
209
+ style: {
210
+ ...children.props.style,
211
+ anchorName: anchor
212
+ }
213
+ } : {},
183
214
  "aria-describedby": [children.props["aria-describedby"], tooltipId].filter(Boolean).join(" ")
184
215
  });
185
216
  }
@@ -187,7 +218,10 @@ var Tooltip = forwardRef(({
187
218
  ...props,
188
219
  ref,
189
220
  className: tooltipWrapperClass,
190
- style: { ...style, anchorName: anchor },
221
+ style: {
222
+ ...style,
223
+ ...nativeInterest ? {} : { anchorName: anchor }
224
+ },
191
225
  "aria-describedby": hasTitle && trigger === children ? [props["aria-describedby"], tooltipId].filter(Boolean).join(" ") : props["aria-describedby"],
192
226
  onMouseEnter: showTooltip,
193
227
  onMouseLeave: hideTooltip,
@@ -1 +1 @@
1
- export declare const version = "0.5.2";
1
+ export declare const version = "0.5.4";