@mendylanda/ui 0.1.0 → 0.1.1

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.
@@ -12,7 +12,7 @@ export function FilterChip({ className, ...props }) {
12
12
  return (_jsx("div", { "data-mendy-ui": "", "data-slot": "filter-chip", className: cn("mui-123306850fe1 mui-539ae144f076 mui-81b198a2db84 mui-d5111d0e9f48 mui-3fa8c572949b mui-71556df3b421 mui-d191cb569aac mui-c74ab393b96d mui-35f35c41d134 mui-5e49f8678d00 mui-3bc474178684 motion-safe:slide-in-from-bottom-2.5 mui-b225f0361d28 mui-bc94a66158a3", className), ...props }));
13
13
  }
14
14
  export function FilterEditorTrigger({ className, asChild, ...props }) {
15
- return (_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { asChild: asChild, variant: "ghost", "data-mendy-ui": "", "data-slot": "filter-editor-trigger", "aria-haspopup": "dialog", className: cn("mui-222f930b8752 mui-57c930dc1c31 mui-66b490dd6005 mui-52417259a4f5 mui-52101fc7d8bb mui-184ddc11e5f9 mui-71556df3b421 mui-70007b876865 mui-e7e01cc7f4df mui-1a5d10977b93 mui-37ad15a95595 mui-4d6720d47806 mui-2e357972fc10 mui-355efe22aedb mui-50aafa8694bb mui-db549d38ff19 mui-8d3c1014b967 mui-4600b77303d6 mui-4027d9c589b6 mui-6f73a280432e", className), ...props }) }));
15
+ return (_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { asChild: asChild, variant: "ghost", "data-mendy-ui": "", "data-slot": "filter-editor-trigger", "aria-haspopup": "dialog", className: cn("mui-222f930b8752 mui-57c930dc1c31 mui-66b490dd6005 mui-52417259a4f5 mui-52101fc7d8bb mui-184ddc11e5f9 mui-71556df3b421 mui-70007b876865 mui-e7e01cc7f4df mui-e9347b78185f mui-37ad15a95595 mui-4d6720d47806 mui-2e357972fc10 mui-355efe22aedb mui-50aafa8694bb mui-db549d38ff19 mui-8d3c1014b967 mui-4600b77303d6 mui-4027d9c589b6 mui-6f73a280432e", className), ...props }) }));
16
16
  }
17
17
  export function FilterEditorContent({ className, onClick, ...props }) {
18
18
  return (_jsx(DropdownMenuContent, { "data-mendy-ui": "", "data-slot": "filter-editor-content", role: "dialog", "aria-orientation": undefined, align: "start", sideOffset: 6, loop: true, onClick: (event) => {
@@ -21,7 +21,7 @@ export function FilterEditorContent({ className, onClick, ...props }) {
21
21
  }, className: cn("mui-dcd942c18ddc mui-34055e6f8c1b mui-2368e909f3a5 mui-04760bcd507f mui-37d8a3f448ff", className), ...props }));
22
22
  }
23
23
  export function FilterRemove({ className, children, ...props }) {
24
- return (_jsx(Button, { variant: "ghost", type: "button", "data-mendy-ui": "", "data-slot": "filter-remove", className: cn("mui-222f930b8752 mui-57c930dc1c31 mui-52417259a4f5 mui-04760bcd507f mui-8867d85ed290 mui-27ead27a81df mui-71556df3b421 mui-a503dd374cca mui-8d940fccb093 mui-163a623b3716 mui-37ad15a95595 mui-4d6720d47806 mui-2e357972fc10 mui-355efe22aedb mui-50aafa8694bb mui-db549d38ff19 mui-8d3c1014b967 mui-4600b77303d6", className), ...props, children: children ?? _jsx(XIcon, { className: "mui-61c000c8a98a", "aria-hidden": "true" }) }));
24
+ return (_jsx(Button, { variant: "ghost", type: "button", "data-mendy-ui": "", "data-slot": "filter-remove", className: cn("mui-222f930b8752 mui-57c930dc1c31 mui-52417259a4f5 mui-04760bcd507f mui-8867d85ed290 mui-27ead27a81df mui-71556df3b421 mui-a503dd374cca mui-55860251be20 mui-163a623b3716 mui-37ad15a95595 mui-4d6720d47806 mui-2e357972fc10 mui-355efe22aedb mui-50aafa8694bb mui-db549d38ff19 mui-8d3c1014b967 mui-4600b77303d6", className), ...props, children: children ?? _jsx(XIcon, { className: "mui-61c000c8a98a", "aria-hidden": "true" }) }));
25
25
  }
26
26
  /** Editing and removal are independent. Values remain owned by the caller. */
27
27
  export function AppliedFilter({ label, editor, onRemove, disabled, open, onOpenChange, editLabel = `Edit ${label} filter`, removeLabel = `Remove ${label} filter`, contentProps, triggerProps, removeProps, children, ...props }) {
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { useValueDraft } from "./use-value-draft.js";
2
3
  import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
3
4
  export const FilterOptionCache = createContext(null);
4
5
  function acquire(cache, key, load) {
@@ -39,6 +40,7 @@ function acquire(cache, key, load) {
39
40
  let released = false;
40
41
  return {
41
42
  promise: current.promise,
43
+ signal: current.abort.signal,
42
44
  release() {
43
45
  if (released)
44
46
  return;
@@ -71,12 +73,12 @@ export function useFilterOptions(id, field, value, enabled) {
71
73
  useLayoutEffect(() => {
72
74
  latest.current = source;
73
75
  }, [source]);
74
- const [localQuery, setLocalQuery] = useState("");
76
+ const scopeKey = JSON.stringify([id, source?.kind, source?.scope, source?.params]);
77
+ const [localQuery, setLocalQuery] = useValueDraft(scopeKey, () => "", `${id}:query`);
75
78
  const query = source?.query ?? localQuery;
76
79
  const [retryKey, retry] = useState(0);
77
- const scopeKey = JSON.stringify([id, source?.scope, source?.params]);
78
80
  const requestKey = JSON.stringify([scopeKey, query, retryKey]);
79
- const identity = JSON.stringify([id, source?.scope]);
81
+ const identity = JSON.stringify([id, source?.kind, source?.scope]);
80
82
  const ids = selectedIds(value);
81
83
  const idsKey = JSON.stringify(ids);
82
84
  const [page, setPage] = useState({ key: "", items: [], loading: false });
@@ -182,7 +184,7 @@ export function useFilterOptions(id, field, value, enabled) {
182
184
  moreRelease.current = request.release;
183
185
  request.promise
184
186
  .then((result) => {
185
- if (currentRequest.current !== requestKey)
187
+ if (request.signal.aborted || currentRequest.current !== requestKey)
186
188
  return;
187
189
  setPage((previous) => ({
188
190
  key: requestKey,
@@ -193,7 +195,7 @@ export function useFilterOptions(id, field, value, enabled) {
193
195
  loading: false,
194
196
  }));
195
197
  }, (error) => {
196
- if (currentRequest.current === requestKey)
198
+ if (!request.signal.aborted && currentRequest.current === requestKey)
197
199
  setPage((previous) => ({ ...previous, loading: false, error: errorMessage(error) }));
198
200
  })
199
201
  .finally(request.release);
@@ -0,0 +1,10 @@
1
+ import type { PointerEvent, RefObject } from "react";
2
+ /** Allow a short diagonal path from a filter row into its adjacent editor. */
3
+ export declare function useMenuPointer({ editor, selectedId, onChoose, }: {
4
+ editor: RefObject<HTMLDivElement | null>;
5
+ selectedId?: string;
6
+ onChoose(id: string): void;
7
+ }): {
8
+ move: (id: string, event: PointerEvent<HTMLButtonElement>) => void;
9
+ cancel: () => void;
10
+ };
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { useEffect, useLayoutEffect, useRef } from "react";
3
+ /** Allow a short diagonal path from a filter row into its adjacent editor. */
4
+ export function useMenuPointer({ editor, selectedId, onChoose, }) {
5
+ const origin = useRef(null);
6
+ const last = useRef(null);
7
+ const pending = useRef(null);
8
+ const candidate = useRef(null);
9
+ const choose = useRef(onChoose);
10
+ useLayoutEffect(() => {
11
+ choose.current = onChoose;
12
+ });
13
+ function cancel() {
14
+ if (pending.current !== null)
15
+ clearTimeout(pending.current);
16
+ pending.current = null;
17
+ candidate.current = null;
18
+ }
19
+ useEffect(() => cancel, []);
20
+ useEffect(() => {
21
+ cancel();
22
+ origin.current = null;
23
+ }, [selectedId]);
24
+ function move(id, event) {
25
+ if (event.pointerType !== "mouse")
26
+ return;
27
+ const point = { x: event.clientX, y: event.clientY };
28
+ const previous = last.current;
29
+ last.current = point;
30
+ // Layout changes beneath a stationary cursor are not navigation intent.
31
+ if (previous?.x === point.x && previous.y === point.y)
32
+ return;
33
+ if (id === selectedId) {
34
+ cancel();
35
+ origin.current = point;
36
+ return;
37
+ }
38
+ const start = origin.current;
39
+ const bounds = editor.current?.getBoundingClientRect();
40
+ if (start && previous && bounds) {
41
+ const edge = start.x < bounds.left ? bounds.left : bounds.right;
42
+ const distance = edge - start.x;
43
+ const progress = (point.x - start.x) / distance;
44
+ const towardsEditor = (point.x - previous.x) * distance > 0;
45
+ const top = start.y + (bounds.top - 8 - start.y) * progress;
46
+ const bottom = start.y + (bounds.bottom + 8 - start.y) * progress;
47
+ if (towardsEditor && progress >= 0 && progress <= 1 && point.y >= top && point.y <= bottom) {
48
+ candidate.current = id;
49
+ if (pending.current === null) {
50
+ pending.current = setTimeout(() => {
51
+ const next = candidate.current;
52
+ cancel();
53
+ if (next)
54
+ choose.current(next);
55
+ }, 300);
56
+ }
57
+ return;
58
+ }
59
+ }
60
+ cancel();
61
+ choose.current(id);
62
+ }
63
+ return { move, cancel };
64
+ }
@@ -1,2 +1,9 @@
1
- /** Preserve unfinished edits until the application's committed value changes. */
2
- export declare function useValueDraft<V, D>(value: V, createDraft: (value: V) => D): readonly [D, (draft: D) => void];
1
+ interface SavedDraft {
2
+ value: unknown;
3
+ draft: unknown;
4
+ }
5
+ export type DraftCache = Map<string, SavedDraft>;
6
+ export declare const FilterDraftCache: import("react").Context<DraftCache | null>;
7
+ /** Keep a menu-session draft until its committed value changes. Outside a menu, state stays local. */
8
+ export declare function useValueDraft<V, D>(value: V, createDraft: (value: V) => D, key?: string): readonly [D, (draft: D) => void];
9
+ export {};
@@ -1,16 +1,31 @@
1
1
  "use client";
2
- import { useState } from "react";
2
+ import { createContext, useContext, useLayoutEffect, useState } from "react";
3
3
  import { equalValues } from "./filter-definition.js";
4
- /** Preserve unfinished edits until the application's committed value changes. */
5
- export function useValueDraft(value, createDraft) {
6
- const [state, setState] = useState(() => ({ value, draft: createDraft(value) }));
4
+ export const FilterDraftCache = createContext(null);
5
+ /** Keep a menu-session draft until its committed value changes. Outside a menu, state stays local. */
6
+ export function useValueDraft(value, createDraft, key) {
7
+ const cache = useContext(FilterDraftCache);
8
+ const [state, setState] = useState(() => {
9
+ const saved = key ? cache?.get(key) : undefined;
10
+ return {
11
+ value,
12
+ draft: saved && equalValues(saved.value, value) ? saved.draft : createDraft(value),
13
+ };
14
+ });
7
15
  let current = state;
8
16
  if (!equalValues(state.value, value)) {
9
17
  current = { value, draft: createDraft(value) };
10
18
  setState(current);
11
19
  }
20
+ useLayoutEffect(() => {
21
+ if (key)
22
+ cache?.set(key, current);
23
+ });
12
24
  function setDraft(draft) {
13
- setState({ value, draft });
25
+ const next = { value, draft };
26
+ if (key)
27
+ cache?.set(key, next);
28
+ setState(next);
14
29
  }
15
30
  return [current.draft, setDraft];
16
31
  }
@@ -35,7 +35,7 @@ function Calendar({ className, classNames, showOutsideDays = true, captionLayout
35
35
  range_start: cn("mui-10c0bae8cba9 mui-292affc1f780", defaultClassNames.range_start),
36
36
  range_middle: cn("mui-52417259a4f5", defaultClassNames.range_middle),
37
37
  range_end: cn("mui-34bbd346e74c mui-292affc1f780", defaultClassNames.range_end),
38
- today: cn("mui-3fa8c572949b mui-292affc1f780 mui-cc209238d847 mui-3cc0d07ef843", defaultClassNames.today),
38
+ today: cn("mui-3fa8c572949b mui-cc310a330231 mui-b7ff49878425 mui-d9bdd2c94504 mui-3cc0d07ef843", defaultClassNames.today),
39
39
  outside: cn("mui-35f35c41d134 mui-7d900a71e5ba", defaultClassNames.outside),
40
40
  disabled: cn("mui-35f35c41d134 mui-0b5a4d1257ab", defaultClassNames.disabled),
41
41
  hidden: cn("mui-b75af7ef6c4d", defaultClassNames.hidden),
@@ -70,6 +70,6 @@ function CalendarDayButton({ className, day, modifiers, ...props }) {
70
70
  return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", "data-day": format(day.date, "yyyy-MM-dd"), "data-selected-single": modifiers.selected &&
71
71
  !modifiers.range_start &&
72
72
  !modifiers.range_end &&
73
- !modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("mui-222f930b8752 mui-129d6f9b1e07 mui-4dad94bd15e3 mui-58d0413dd93c mui-1458e495e3ae mui-302c0d124a94 mui-19e97f3f40e6 mui-761cdb81bddb mui-52101fc7d8bb mui-60052b901494 mui-3f96bf138f7b mui-d39eb62b8363 mui-47cbd20a4dc0 mui-4eb7d653ae35 mui-96308b55e0d6 mui-a55dbe6f3eec mui-714051e55e8e mui-c764cebcd540 mui-05ec0700cd48 mui-5fb598fb06f4 mui-0813905aa141 mui-8020e31bff67 mui-ab65911baf1d mui-f5460d79341c mui-421a7d3e6631 mui-a39af81d518c mui-b6991ab170de mui-b2786f463a62 mui-ba417cf466b0 mui-829c8ef4f9c5", defaultClassNames.day, className), ...props }));
73
+ !modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("mui-222f930b8752 mui-129d6f9b1e07 mui-4dad94bd15e3 mui-58d0413dd93c mui-1458e495e3ae mui-302c0d124a94 mui-19e97f3f40e6 mui-761cdb81bddb mui-52101fc7d8bb mui-60052b901494 mui-3f96bf138f7b mui-d39eb62b8363 mui-5892a880410a mui-31b75c623c06 mui-96308b55e0d6 mui-a55dbe6f3eec mui-714051e55e8e mui-c764cebcd540 mui-05ec0700cd48 mui-5fb598fb06f4 mui-0813905aa141 mui-8020e31bff67 mui-ab65911baf1d mui-f5460d79341c mui-421a7d3e6631 mui-a39af81d518c mui-b6991ab170de mui-b2786f463a62 mui-ba417cf466b0 mui-829c8ef4f9c5", defaultClassNames.day, className), ...props }));
74
74
  }
75
75
  export { Calendar, CalendarDayButton };
@@ -25,13 +25,13 @@ function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
25
25
  return (_jsx(DropdownMenuPrimitive.Item, { "data-mendy-ui": "", "data-slot": "dropdown-menu-item", "data-inset": inset, "data-variant": variant, className: cn("mui-d2d9e1f13413 mui-222f930b8752 mui-d343f41ba73e mui-71556df3b421 mui-074569488cca mui-02e603944040 mui-e7e01cc7f4df mui-a8c1dfb1d4ef mui-c74ab393b96d mui-59edd3d33673 mui-78905c9e865c mui-a8c1861c5d03 mui-a85c4c5d7465 mui-397f5ba426a0 mui-9beb654fcb0d mui-6fa6a8ade7c4 mui-f3f7a9c5e1b3 mui-e2bb4ac97190 mui-353a727facfd mui-98a28c2293f7 mui-bcae9d37090c mui-040e748a3fa0 mui-eb5ef2dbc2d4 mui-8637dfd54fbb mui-82b114bbff3d", className), ...props }));
26
26
  }
27
27
  function DropdownMenuCheckboxItem({ className, children, checked, ...props }) {
28
- return (_jsxs(DropdownMenuPrimitive.CheckboxItem, { "data-mendy-ui": "", "data-slot": "dropdown-menu-checkbox-item", className: cn("mui-d2d9e1f13413 mui-222f930b8752 mui-d343f41ba73e mui-71556df3b421 mui-074569488cca mui-02e603944040 mui-a8c1dfb1d4ef mui-98a5b6fff1d2 mui-75b27e813dcc mui-c74ab393b96d mui-59edd3d33673 mui-78905c9e865c mui-a8c1861c5d03 mui-a85c4c5d7465 mui-397f5ba426a0 mui-9beb654fcb0d mui-bcae9d37090c mui-040e748a3fa0 mui-eb5ef2dbc2d4", className), checked: checked, ...props, children: [_jsx("span", { className: "mui-33228ff7d583 mui-747355bdc2a2 mui-5b9fdf1819c4 mui-222f930b8752 mui-61c000c8a98a mui-71556df3b421 mui-a503dd374cca", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, { className: "mui-100c22d5776b" }) }) }), children] }));
28
+ return (_jsxs(DropdownMenuPrimitive.CheckboxItem, { "data-mendy-ui": "", "data-slot": "dropdown-menu-checkbox-item", className: cn("mui-d2d9e1f13413 mui-222f930b8752 mui-d343f41ba73e mui-71556df3b421 mui-074569488cca mui-02e603944040 mui-a8c1dfb1d4ef mui-05d4103bd67f mui-078d945937da mui-c74ab393b96d mui-59edd3d33673 mui-78905c9e865c mui-a8c1861c5d03 mui-a85c4c5d7465 mui-397f5ba426a0 mui-9beb654fcb0d mui-bcae9d37090c mui-040e748a3fa0 mui-eb5ef2dbc2d4", className), checked: checked, ...props, children: [_jsx("span", { className: "mui-33228ff7d583 mui-747355bdc2a2 mui-207bd0b3f259 mui-222f930b8752 mui-61c000c8a98a mui-71556df3b421 mui-a503dd374cca", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, { className: "mui-100c22d5776b" }) }) }), children] }));
29
29
  }
30
30
  function DropdownMenuRadioGroup({ ...props }) {
31
31
  return (_jsx(DropdownMenuPrimitive.RadioGroup, { "data-mendy-ui": "", "data-slot": "dropdown-menu-radio-group", ...props }));
32
32
  }
33
33
  function DropdownMenuRadioItem({ className, children, ...props }) {
34
- return (_jsxs(DropdownMenuPrimitive.RadioItem, { "data-mendy-ui": "", "data-slot": "dropdown-menu-radio-item", className: cn("mui-d2d9e1f13413 mui-222f930b8752 mui-d343f41ba73e mui-71556df3b421 mui-074569488cca mui-02e603944040 mui-a8c1dfb1d4ef mui-98a5b6fff1d2 mui-75b27e813dcc mui-c74ab393b96d mui-59edd3d33673 mui-78905c9e865c mui-a8c1861c5d03 mui-a85c4c5d7465 mui-397f5ba426a0 mui-9beb654fcb0d mui-bcae9d37090c mui-040e748a3fa0 mui-eb5ef2dbc2d4", className), ...props, children: [_jsx("span", { className: "mui-33228ff7d583 mui-747355bdc2a2 mui-5b9fdf1819c4 mui-222f930b8752 mui-61c000c8a98a mui-71556df3b421 mui-a503dd374cca", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CircleIcon, { className: "mui-9e7600197f38 mui-717b342c2d01" }) }) }), children] }));
34
+ return (_jsxs(DropdownMenuPrimitive.RadioItem, { "data-mendy-ui": "", "data-slot": "dropdown-menu-radio-item", className: cn("mui-d2d9e1f13413 mui-222f930b8752 mui-d343f41ba73e mui-71556df3b421 mui-074569488cca mui-02e603944040 mui-a8c1dfb1d4ef mui-05d4103bd67f mui-078d945937da mui-c74ab393b96d mui-59edd3d33673 mui-78905c9e865c mui-a8c1861c5d03 mui-a85c4c5d7465 mui-397f5ba426a0 mui-9beb654fcb0d mui-bcae9d37090c mui-040e748a3fa0 mui-eb5ef2dbc2d4", className), ...props, children: [_jsx("span", { className: "mui-33228ff7d583 mui-747355bdc2a2 mui-207bd0b3f259 mui-222f930b8752 mui-61c000c8a98a mui-71556df3b421 mui-a503dd374cca", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CircleIcon, { className: "mui-9e7600197f38 mui-717b342c2d01" }) }) }), children] }));
35
35
  }
36
36
  function DropdownMenuLabel({ className, inset, ...props }) {
37
37
  return (_jsx(DropdownMenuPrimitive.Label, { "data-mendy-ui": "", "data-slot": "dropdown-menu-label", "data-inset": inset, className: cn("mui-e7e01cc7f4df mui-a8c1dfb1d4ef mui-c74ab393b96d mui-daaac3fbf55e mui-6fa6a8ade7c4", className), ...props }));