@enigmax/primitives 0.22.0 → 0.24.0

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 (79) hide show
  1. package/dist/{chunk-QQFNAKMY.js → chunk-45UHLZYT.js} +1 -1
  2. package/dist/chunk-4VUHQFAT.js +130 -0
  3. package/dist/{chunk-D5A2ZMAG.js → chunk-4ZPMP47J.js} +6 -1
  4. package/dist/chunk-DZMHY3SU.js +777 -0
  5. package/dist/{chunk-FWVWX67R.js → chunk-FGBIZDV2.js} +3 -3
  6. package/dist/chunk-N6PDHMAX.js +213 -0
  7. package/dist/chunk-NONGREXC.js +248 -0
  8. package/dist/{chunk-R4ZAEE7V.js → chunk-QJJ34E4G.js} +34 -10
  9. package/dist/chunk-S2FPN5ID.js +115 -0
  10. package/dist/chunk-S7EE57YB.js +9 -0
  11. package/dist/chunk-VKL3DEIQ.js +804 -0
  12. package/dist/chunk-WSQC3PCC.js +466 -0
  13. package/dist/chunk-Z3VDE7OA.js +532 -0
  14. package/dist/clipboard-menu-B_ouitfS.d.ts +264 -0
  15. package/dist/color-D_rZ83Oc.d.ts +152 -0
  16. package/dist/color-OPV3BJV6.js +452 -0
  17. package/dist/{index-BHpOZncw.d.ts → index-ZPvlf9vs.d.ts} +53 -5
  18. package/dist/index.d.ts +6 -2
  19. package/dist/index.js +9 -4
  20. package/dist/keys-D2zJs1uB.d.ts +100 -0
  21. package/dist/next/index.d.ts +10 -3
  22. package/dist/next/index.js +22 -13
  23. package/dist/react/context-menu.d.ts +219 -0
  24. package/dist/react/context-menu.js +6 -0
  25. package/dist/react/index.d.ts +12 -5
  26. package/dist/react/index.js +21 -12
  27. package/dist/react/input.d.ts +3 -3
  28. package/dist/react/input.js +2 -1
  29. package/dist/react/palette.d.ts +1 -1
  30. package/dist/react/palette.js +3 -3
  31. package/dist/react/search.d.ts +1 -1
  32. package/dist/react/search.js +2 -2
  33. package/dist/react/select.d.ts +217 -0
  34. package/dist/react/select.js +7 -0
  35. package/dist/react/selection.d.ts +104 -0
  36. package/dist/react/selection.js +4 -0
  37. package/dist/react-router/index.d.ts +10 -3
  38. package/dist/react-router/index.js +22 -13
  39. package/dist/search/index.d.ts +2 -2
  40. package/dist/search/index.js +1 -1
  41. package/dist/{search-BD9-5O5U.d.ts → search-DYgqRp37.d.ts} +13 -1
  42. package/dist/{search-UQEXAPQB.js → search-PBZORZ7P.js} +1 -1
  43. package/dist/select-ClSy-J1f.d.ts +100 -0
  44. package/dist/selection-B_pmzHpy.d.ts +150 -0
  45. package/package.json +23 -2
  46. package/recipes/color/styles.css +143 -0
  47. package/recipes/context-menu/styles.css +185 -0
  48. package/recipes/input/styles.css +9 -0
  49. package/recipes/palette/styles.css +3 -0
  50. package/recipes/search/tailwind.tsx +3 -2
  51. package/recipes/select/styles.css +230 -0
  52. package/recipes/toast/styles.css +1 -1
  53. package/registry.json +432 -5
  54. package/src/core/clipboard-menu.ts +270 -0
  55. package/src/core/color.ts +248 -0
  56. package/src/core/context-menu.ts +694 -0
  57. package/src/core/keys.ts +264 -0
  58. package/src/core/search.ts +19 -0
  59. package/src/core/select.ts +404 -0
  60. package/src/core/selection.ts +648 -0
  61. package/src/index.ts +92 -1
  62. package/src/react/context-menu/context.ts +64 -0
  63. package/src/react/context-menu/index.tsx +108 -0
  64. package/src/react/context-menu/root.tsx +970 -0
  65. package/src/react/context-menu/styles.ts +194 -0
  66. package/src/react/index.ts +100 -1
  67. package/src/react/input/color-styles.ts +156 -0
  68. package/src/react/input/color.tsx +466 -0
  69. package/src/react/input/index.tsx +54 -6
  70. package/src/react/input/types.ts +59 -3
  71. package/src/react/palette/root.tsx +2 -2
  72. package/src/react/select/context.ts +56 -0
  73. package/src/react/select/index.tsx +96 -0
  74. package/src/react/select/root.tsx +839 -0
  75. package/src/react/select/styles.ts +238 -0
  76. package/src/react/selection/index.tsx +115 -0
  77. package/src/react/selection/use-selection.ts +260 -0
  78. package/dist/password-C8lG4Zm9.d.ts +0 -71
  79. /package/dist/{chunk-U3V4EHOB.js → chunk-MSOCCQGH.js} +0 -0
@@ -3,7 +3,7 @@
3
3
  import { Slot } from "@/react/slot";
4
4
  import { createPortal } from "react-dom";
5
5
  import { PaletteContext, usePaletteContext, type PaletteRow } from "@/react/palette/context";
6
- import { createSearch, subsequenceMatcher, type SearchInstance, type SearchMatch, type SearchOptions } from "@/core/search";
6
+ import { createSearch, subsequenceMatcher, shortenQuery, type SearchInstance, type SearchMatch, type SearchOptions } from "@/core/search";
7
7
  import { createRecentStore, groupRows, moveActive, shortcutLabel, isPaletteShortcut, type RecentEntry, type PaletteKey } from "@/core/palette";
8
8
  import {
9
9
  useCallback, useEffect, useId, useMemo, useRef, useState,
@@ -576,7 +576,7 @@ export function PaletteList<Item>({ children, empty, heading, ...props }: Palett
576
576
  data-enigma-palette-list=""
577
577
  >
578
578
  {palette.rows.length === 0
579
- ? empty ?? <p data-enigma-palette-empty="">{palette.query.trim() ? `Nothing matches "${palette.query.trim()}".` : "Type to search."}</p>
579
+ ? empty ?? <p data-enigma-palette-empty="">{palette.query.trim() ? `Nothing matches "${shortenQuery(palette.query)}".` : "Type to search."}</p>
580
580
  : groups.map((group) => (
581
581
  <div key={group.label} role="group" aria-label={group.label} data-enigma-palette-group="">
582
582
  {heading !== null && (
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext } from "react";
4
+ import type { ReactNode, RefObject } from "react";
5
+ import type { SelectInstance, SelectOption, SelectState } from "@/core/select";
6
+
7
+ /**
8
+ * One option, as React draws it. The core carries `icon` as `unknown` because it cannot
9
+ * know what a node is; here it is one.
10
+ */
11
+ export interface SelectItem extends SelectOption {
12
+ icon?: ReactNode;
13
+ }
14
+
15
+ export interface SelectContextValue {
16
+ instance: SelectInstance;
17
+ state: SelectState;
18
+ /** Open or close the panel. React owns that flag; the instance follows it. */
19
+ setOpen: (open: boolean) => void;
20
+ disabled: boolean;
21
+ /** Whether the trigger offers a × that empties the selection. */
22
+ clearable: boolean;
23
+ searchable: boolean;
24
+ /**
25
+ * There is nothing to choose from at all - not a filter that matched nothing, an option
26
+ * list that is empty. The trigger says so and refuses to open, because a panel holding one
27
+ * line of apology is worse than a control that reads as unavailable.
28
+ */
29
+ empty: boolean;
30
+ /** The options have not arrived yet, which is a different thing from there being none. */
31
+ loading: boolean;
32
+ emptyLabel: ReactNode;
33
+ loadingLabel: ReactNode;
34
+ /** ids the parts need to point at each other. */
35
+ ids: { trigger: string; list: string; field: string; };
36
+ optionId: (index: number) => string;
37
+ triggerRef: RefObject<HTMLButtonElement | null>;
38
+ fieldRef: RefObject<HTMLInputElement | null>;
39
+ /** Close and put focus back where it came from - the trigger, always. */
40
+ close: () => void;
41
+ /** The keys the list answers to, shared by the trigger and the search field. */
42
+ onListKeyDown: (event: React.KeyboardEvent) => void;
43
+ }
44
+
45
+ /**
46
+ * Null rather than a default: a part rendered outside its Root is a mistake with an obvious
47
+ * fix, and a silent default would leave it half-working - a trigger that opens nothing, an
48
+ * option that selects into the void.
49
+ */
50
+ export const SelectContext = createContext<SelectContextValue | null>(null);
51
+
52
+ export function useSelectContext(part: string): SelectContextValue {
53
+ const value = useContext(SelectContext);
54
+ if (!value) throw new Error(`<${part}> must be rendered inside <Select.Root>.`);
55
+ return value;
56
+ }
@@ -0,0 +1,96 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import * as parts from "@/react/select/root";
5
+ import { useSelectContext } from "@/react/select/context";
6
+ import type { SelectRootProps, SelectTriggerProps, SelectContentProps, SelectListProps } from "@/react/select/root";
7
+
8
+ export type SelectProps = SelectRootProps & {
9
+ /** What the trigger says with nothing chosen. */
10
+ placeholder?: ReactNode;
11
+ searchPlaceholder?: string;
12
+ /** Removable tags instead of a comma-separated line. Default: on when `multiple`. */
13
+ tags?: boolean;
14
+ maxTags?: number;
15
+ /** Shown when the filter matches nothing. */
16
+ empty?: ReactNode;
17
+ /** Draw a row yourself: the icon, label, description and check are the default. */
18
+ renderOption?: SelectListProps["children"];
19
+ /** On the root, which is the element with a size. `triggerProps` dresses the button. */
20
+ className?: string;
21
+ triggerProps?: SelectTriggerProps;
22
+ contentProps?: SelectContentProps;
23
+ };
24
+
25
+ /**
26
+ * The select, assembled.
27
+ *
28
+ * ```tsx
29
+ * <Select
30
+ * options={[{ value: "es", label: "Spain", icon: <Flag code="es" /> }]}
31
+ * value={country}
32
+ * onValueChange={setCountry}
33
+ * />
34
+ * ```
35
+ *
36
+ * Everything it renders is a part you can render yourself instead - see `Select.Root` for
37
+ * the anatomy. This is those parts in the order they belong in, not a different component.
38
+ *
39
+ * `multiple` changes the types with it: `value` and what the change reports are a list, and
40
+ * the compiler says so at the call site. A filter appears on its own once the list is long
41
+ * enough to need one, fuzzy if you hand over Fuse's constructor and plain otherwise.
42
+ */
43
+ export function Select(props: SelectProps): ReactNode {
44
+ const {
45
+ placeholder = "Select",
46
+ searchPlaceholder = "Search",
47
+ tags,
48
+ maxTags,
49
+ empty,
50
+ renderOption,
51
+ className,
52
+ triggerProps,
53
+ contentProps,
54
+ children,
55
+ ...rest
56
+ } = props;
57
+
58
+ // The union survives as far as the props object; the rest spread flattens it, so the
59
+ // cast puts back what TypeScript dropped rather than widening anything.
60
+ const root = rest as SelectRootProps;
61
+
62
+ return (
63
+ <parts.SelectRoot {...root} className={className}>
64
+ <parts.SelectTrigger {...triggerProps}>
65
+ <parts.SelectValue placeholder={placeholder} tags={tags} maxTags={maxTags} />
66
+ </parts.SelectTrigger>
67
+ <parts.SelectContent {...contentProps}>
68
+ {/* Rendered on the root's own decision: `searchable="auto"` is a count, and
69
+ the field has to appear on the same rule the keyboard follows. */}
70
+ <SearchSlot placeholder={searchPlaceholder} />
71
+ <parts.SelectList empty={empty}>{renderOption}</parts.SelectList>
72
+ </parts.SelectContent>
73
+ {children}
74
+ </parts.SelectRoot>
75
+ );
76
+ }
77
+
78
+ /** The filter, only where the root decided there is one. */
79
+ function SearchSlot({ placeholder }: { placeholder: string; }): ReactNode {
80
+ const { searchable } = useSelectContext("Select");
81
+ return searchable ? <parts.SelectSearch placeholder={placeholder} /> : null;
82
+ }
83
+
84
+ Select.Root = parts.SelectRoot;
85
+ Select.Trigger = parts.SelectTrigger;
86
+ Select.Value = parts.SelectValue;
87
+ Select.Content = parts.SelectContent;
88
+ Select.Search = parts.SelectSearch;
89
+ Select.List = parts.SelectList;
90
+ Select.Option = parts.SelectOptionRow;
91
+
92
+ export * from "@/react/select/root";
93
+ export { useSelectContext, type SelectItem, type SelectContextValue } from "@/react/select/context";
94
+ export { SELECT_STYLES } from "@/react/select/styles";
95
+ export { createSelect, SELECT_SEARCH_KEYS } from "@/core/select";
96
+ export type { SelectOption, SelectOptions, SelectState, SelectInstance, SelectMoveKey } from "@/core/select";