@boxcustodia/library 2.0.0-alpha.32 → 2.0.0-alpha.34

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,5 +1,6 @@
1
1
  import { Select as SelectPrimitive } from '@base-ui/react/select';
2
2
  import { useRender } from '@base-ui/react/use-render';
3
+ import { InferItemId, ItemId } from '../../utils/internal';
3
4
  import * as React from "react";
4
5
  export declare const selectTriggerClasses: string;
5
6
  export declare const selectTriggerIconClassName = "-me-1 size-4.5 opacity-80 sm:size-4";
@@ -29,21 +30,7 @@ export declare function SelectSeparator({ className, ...props }: SelectPrimitive
29
30
  export declare function SelectGroup(props: SelectPrimitive.Group.Props): React.ReactElement;
30
31
  export declare function SelectGroupLabel(props: SelectPrimitive.GroupLabel.Props): React.ReactElement;
31
32
  export declare function SelectLabel({ className, ...props }: SelectPrimitive.Label.Props): React.ReactElement;
32
- /** Identifier an item can be addressed by. */
33
- type SelectId = string | number;
34
- /**
35
- * Infers the id type from the shape of an item, mirroring `defaultGetId`'s
36
- * lookup order (`id` before `value`, then the primitive itself). Lets the
37
- * common case — `{ id, name }`, `{ label, value }`, or plain primitives — drive
38
- * `value` / `onValueChange` typing with no explicit `getId`. Falls back to
39
- * `string` for shapes `defaultGetId` can't address.
40
- */
41
- type InferId<TItem> = TItem extends {
42
- id: infer I extends SelectId;
43
- } ? I : TItem extends {
44
- value: infer V extends SelectId;
45
- } ? V : TItem extends SelectId ? TItem : string;
46
- type SelectBaseProps<TItem, TId extends SelectId> = Omit<SelectPrimitive.Root.Props<string, false>, "children" | "onValueChange" | "value" | "defaultValue" | "items" | "multiple"> & {
33
+ type SelectBaseProps<TItem, TId extends ItemId> = Omit<SelectPrimitive.Root.Props<string, false>, "children" | "onValueChange" | "value" | "defaultValue" | "items" | "multiple"> & {
47
34
  items: readonly TItem[];
48
35
  /** Returns the display text for an item. Used for the trigger value and ARIA. Defaults to `item.label`, `item.name`, `item.title`, or the stringified primitive. */
49
36
  getLabel?: (item: TItem) => string;
@@ -64,7 +51,7 @@ type SelectBaseProps<TItem, TId extends SelectId> = Omit<SelectPrimitive.Root.Pr
64
51
  item?: string;
65
52
  };
66
53
  };
67
- export type SelectProps<TItem = unknown, TId extends SelectId = InferId<TItem>> = (SelectBaseProps<TItem, TId> & {
54
+ export type SelectProps<TItem = unknown, TId extends ItemId = InferItemId<TItem>> = (SelectBaseProps<TItem, TId> & {
68
55
  multiple?: false;
69
56
  /** Controlled selection. The id (whatever `getId` returns — `string` or `number`), or `null` when cleared. */
70
57
  value?: TId | null;
@@ -99,6 +86,6 @@ export type SelectProps<TItem = unknown, TId extends SelectId = InferId<TItem>>
99
86
  * Use `SelectRoot` + primitives directly when you need full structural
100
87
  * control beyond what `className` + `classNames` slots offer.
101
88
  */
102
- export declare function Select<TItem = unknown, TId extends SelectId = InferId<TItem>>(allProps: SelectProps<TItem, TId>): React.ReactElement;
89
+ export declare function Select<TItem = unknown, TId extends ItemId = InferItemId<TItem>>(allProps: SelectProps<TItem, TId>): React.ReactElement;
103
90
  export { SelectPrimitive };
104
91
  export { SelectPopup as SelectContent };
@@ -0,0 +1,2 @@
1
+ export type { InferItemId, ItemId } from './item';
2
+ export { defaultGetId, defaultGetLabel } from './item';
@@ -0,0 +1,27 @@
1
+ /** Identifier an item can be addressed by. */
2
+ export type ItemId = string | number;
3
+ /**
4
+ * Infers the id type from the shape of an item, mirroring `defaultGetId`'s
5
+ * lookup order (`id` before `value`, then the primitive itself). Lets the
6
+ * common case — `{ id, name }`, `{ label, value }`, or plain primitives — drive
7
+ * `value` / `onValueChange` typing with no explicit `getId`. Falls back to
8
+ * `string` for shapes `defaultGetId` can't address.
9
+ */
10
+ export type InferItemId<TItem> = TItem extends {
11
+ id: infer I extends ItemId;
12
+ } ? I : TItem extends {
13
+ value: infer V extends ItemId;
14
+ } ? V : TItem extends ItemId ? TItem : string;
15
+ /**
16
+ * Resolves the display text for an item. Defaults to `item.label`, `item.name`,
17
+ * `item.title`, or the stringified primitive. Always returns a string — labels
18
+ * are display text.
19
+ */
20
+ export declare function defaultGetLabel(item: unknown): string;
21
+ /**
22
+ * Resolves the id for an item, preserving its native type. The internal Base UI
23
+ * layer keys options by string (callers wrap this in `String(...)`), so keeping
24
+ * the raw type here lets `onValueChange` hand back a numeric id as a number, not
25
+ * a string. Defaults to `item.id`, `item.value`, or the stringified primitive.
26
+ */
27
+ export declare function defaultGetId(item: unknown): ItemId;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function t(r){if(r==null)return"";if(typeof r=="string"||typeof r=="number")return String(r);if(typeof r=="object"){const n=r;if("label"in n)return String(n.label);if("name"in n)return String(n.name);if("title"in n)return String(n.title)}return String(r)}function e(r){if(r==null)return"";if(typeof r=="string"||typeof r=="number")return r;if(typeof r=="object"){const n=r;if("id"in n)return n.id;if("value"in n)return n.value}return String(r)}exports.defaultGetId=e;exports.defaultGetLabel=t;
@@ -0,0 +1,25 @@
1
+ function t(r) {
2
+ if (r == null) return "";
3
+ if (typeof r == "string" || typeof r == "number") return String(r);
4
+ if (typeof r == "object") {
5
+ const n = r;
6
+ if ("label" in n) return String(n.label);
7
+ if ("name" in n) return String(n.name);
8
+ if ("title" in n) return String(n.title);
9
+ }
10
+ return String(r);
11
+ }
12
+ function e(r) {
13
+ if (r == null) return "";
14
+ if (typeof r == "string" || typeof r == "number") return r;
15
+ if (typeof r == "object") {
16
+ const n = r;
17
+ if ("id" in n) return n.id;
18
+ if ("value" in n) return n.value;
19
+ }
20
+ return String(r);
21
+ }
22
+ export {
23
+ e as defaultGetId,
24
+ t as defaultGetLabel
25
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxcustodia/library",
3
- "version": "2.0.0-alpha.32",
3
+ "version": "2.0.0-alpha.34",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -8,11 +8,11 @@ import { BaseButton } from "./components/base-button";
8
8
  const buttonVariants = cva(
9
9
  [
10
10
  "group/button relative inline-flex shrink-0 items-center justify-center gap-1 rounded-lg border border-transparent bg-clip-padding",
11
- "text-sm font-medium whitespace-nowrap transition-all outline-none select-none",
11
+ "text-sm font-medium whitespace-nowrap transition-[background-color,border-color,box-shadow,transform,opacity] outline-none select-none",
12
12
  "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50",
13
13
  "active:not-aria-[haspopup]:translate-y-px",
14
14
  "disabled:pointer-events-none disabled:opacity-50",
15
- "aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
15
+ "aria-invalid:border-error aria-invalid:ring-3 aria-invalid:ring-error/20 dark:aria-invalid:border-error/50 dark:aria-invalid:ring-error/40",
16
16
  "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
17
17
  "[&[data-loading]]:text-transparent",
18
18
  "[&[data-loading]>*:not([data-loader])]:invisible",
@@ -217,6 +217,31 @@ describe("Combobox getId / getLabel defaults", () => {
217
217
  await userEvent.click(getInput());
218
218
  expect(screen.getAllByRole("option").length).toBe(2);
219
219
  });
220
+
221
+ it("returns a numeric id as a number without a custom getId", async () => {
222
+ const numeric = [
223
+ { id: 1, name: "Apple" },
224
+ { id: 2, name: "Banana" },
225
+ ];
226
+ const onValueChange = vi.fn();
227
+ render(
228
+ <Combobox
229
+ items={numeric}
230
+ placeholder="P"
231
+ onValueChange={onValueChange}
232
+ />,
233
+ );
234
+
235
+ const input = getInput();
236
+ await userEvent.click(input);
237
+ await userEvent.type(input, "Banana");
238
+ await userEvent.click(screen.getByRole("option"));
239
+
240
+ // defaultGetId preserves the native id type — number stays a number,
241
+ // not a stringified "2".
242
+ expect(onValueChange.mock.calls[0][0]).toBe(2);
243
+ expect(typeof onValueChange.mock.calls[0][0]).toBe("number");
244
+ });
220
245
  });
221
246
 
222
247
  describe("Combobox (multiple mode)", () => {
@@ -6,6 +6,12 @@ import { ChevronsUpDownIcon, XIcon } from "lucide-react";
6
6
  import * as React from "react";
7
7
  import { useIsomorphicLayoutEffect } from "../../hooks/internal";
8
8
  import { cn } from "../../lib";
9
+ import {
10
+ defaultGetId,
11
+ defaultGetLabel,
12
+ type InferItemId,
13
+ type ItemId,
14
+ } from "../../utils/internal";
9
15
  import { useIsInsideFieldRoot } from "../field";
10
16
  import { Input, inputBaseClasses } from "../input";
11
17
  import { ScrollArea } from "../scroll-area";
@@ -702,29 +708,6 @@ function ComboboxMultipleChipsContent({
702
708
 
703
709
  // ─── Composite ────────────────────────────────────────────────────────────────
704
710
 
705
- function defaultGetLabel(item: unknown): string {
706
- if (item == null) return "";
707
- if (typeof item === "string" || typeof item === "number") return String(item);
708
- if (typeof item === "object") {
709
- const record = item as Record<string, unknown>;
710
- if ("label" in record) return String(record.label);
711
- if ("name" in record) return String(record.name);
712
- if ("title" in record) return String(record.title);
713
- }
714
- return String(item);
715
- }
716
-
717
- function defaultGetId(item: unknown): string {
718
- if (item == null) return "";
719
- if (typeof item === "string" || typeof item === "number") return String(item);
720
- if (typeof item === "object") {
721
- const record = item as Record<string, unknown>;
722
- if ("id" in record) return String(record.id);
723
- if ("value" in record) return String(record.value);
724
- }
725
- return String(item);
726
- }
727
-
728
711
  function ComboboxSingleTrigger({
729
712
  placeholder,
730
713
  showClear,
@@ -777,26 +760,9 @@ function ComboboxMultipleTrigger({
777
760
  );
778
761
  }
779
762
 
780
- type ComboboxId = string | number;
781
-
782
- /**
783
- * Infers the id type from the shape of an item, mirroring `defaultGetId`'s
784
- * lookup order (`id` before `value`, then the primitive itself). Lets the
785
- * common case — `{ id, name }`, `{ label, value }`, or plain primitives — drive
786
- * `value` / `onValueChange` typing with no explicit `getId`. Falls back to
787
- * `string` for shapes `defaultGetId` can't address.
788
- */
789
- type InferId<TItem> = TItem extends { id: infer I extends ComboboxId }
790
- ? I
791
- : TItem extends { value: infer V extends ComboboxId }
792
- ? V
793
- : TItem extends ComboboxId
794
- ? TItem
795
- : string;
796
-
797
763
  type ComboboxBaseProps<
798
764
  TItem = unknown,
799
- TId extends ComboboxId = InferId<TItem>,
765
+ TId extends ItemId = InferItemId<TItem>,
800
766
  > = Omit<
801
767
  ComboboxPrimitive.Root.Props<TItem, boolean>,
802
768
  | "items"
@@ -843,7 +809,7 @@ type ComboboxBaseProps<
843
809
 
844
810
  export type ComboboxProps<
845
811
  TItem = unknown,
846
- TId extends ComboboxId = InferId<TItem>,
812
+ TId extends ItemId = InferItemId<TItem>,
847
813
  > =
848
814
  | (ComboboxBaseProps<TItem, TId> & {
849
815
  multiple?: false;
@@ -881,7 +847,7 @@ export type ComboboxProps<
881
847
  */
882
848
  export function Combobox<
883
849
  TItem = unknown,
884
- TId extends ComboboxId = InferId<TItem>,
850
+ TId extends ItemId = InferItemId<TItem>,
885
851
  >(allProps: ComboboxProps<TItem, TId>): React.ReactElement {
886
852
  // Cast internally — ComboboxProps discriminated union enforces correctness for consumers.
887
853
  // The union can't be spread directly into ComboboxRoot because TypeScript can't resolve
@@ -424,6 +424,24 @@ describe("Select controlled value (id-first)", () => {
424
424
  expect(onChange.mock.calls[0][1]).toEqual(numeric[0]);
425
425
  });
426
426
 
427
+ it("returns a numeric id as a number without a custom getId", async () => {
428
+ const numeric = [
429
+ { id: 1, name: "Aumentar retención" },
430
+ { id: 2, name: "Reducir churn" },
431
+ ];
432
+ const onChange = vi.fn();
433
+ render(<Select items={numeric} defaultOpen onValueChange={onChange} />);
434
+
435
+ await userEvent.click(
436
+ screen.getByRole("option", { name: "Aumentar retención" }),
437
+ );
438
+
439
+ // defaultGetId preserves the native id type — number stays a number,
440
+ // not a stringified "1".
441
+ expect(onChange.mock.calls[0][0]).toBe(1);
442
+ expect(typeof onChange.mock.calls[0][0]).toBe("number");
443
+ });
444
+
427
445
  it("rejects an item object as value at compile time", () => {
428
446
  // @ts-expect-error — value is id-first; passing the item must not compile.
429
447
  render(<Select items={fruits} value={fruits[1]} />);
@@ -10,6 +10,12 @@ import {
10
10
  } from "lucide-react";
11
11
  import * as React from "react";
12
12
  import { cn } from "../../lib";
13
+ import {
14
+ defaultGetId,
15
+ defaultGetLabel,
16
+ type InferItemId,
17
+ type ItemId,
18
+ } from "../../utils/internal";
13
19
  import { inputBaseClasses } from "../input";
14
20
 
15
21
  // ─── Primitives ───────────────────────────────────────────────────────────────
@@ -266,54 +272,13 @@ export function SelectLabel({
266
272
 
267
273
  // ─── Composite ────────────────────────────────────────────────────────────────
268
274
 
269
- function defaultGetLabel(item: unknown): string {
270
- if (item == null) return "";
271
- if (typeof item === "string" || typeof item === "number") return String(item);
272
- if (typeof item === "object") {
273
- const record = item as Record<string, unknown>;
274
- if ("label" in record) return String(record.label);
275
- if ("name" in record) return String(record.name);
276
- if ("title" in record) return String(record.title);
277
- }
278
- return String(item);
279
- }
280
-
281
- function defaultGetId(item: unknown): string {
282
- if (item == null) return "";
283
- if (typeof item === "string" || typeof item === "number") return String(item);
284
- if (typeof item === "object") {
285
- const record = item as Record<string, unknown>;
286
- if ("id" in record) return String(record.id);
287
- if ("value" in record) return String(record.value);
288
- }
289
- return String(item);
290
- }
291
-
292
275
  function defaultGetDisabled(item: unknown): boolean {
293
276
  if (item != null && typeof item === "object" && "disabled" in item)
294
277
  return Boolean((item as Record<string, unknown>).disabled);
295
278
  return false;
296
279
  }
297
280
 
298
- /** Identifier an item can be addressed by. */
299
- type SelectId = string | number;
300
-
301
- /**
302
- * Infers the id type from the shape of an item, mirroring `defaultGetId`'s
303
- * lookup order (`id` before `value`, then the primitive itself). Lets the
304
- * common case — `{ id, name }`, `{ label, value }`, or plain primitives — drive
305
- * `value` / `onValueChange` typing with no explicit `getId`. Falls back to
306
- * `string` for shapes `defaultGetId` can't address.
307
- */
308
- type InferId<TItem> = TItem extends { id: infer I extends SelectId }
309
- ? I
310
- : TItem extends { value: infer V extends SelectId }
311
- ? V
312
- : TItem extends SelectId
313
- ? TItem
314
- : string;
315
-
316
- type SelectBaseProps<TItem, TId extends SelectId> = Omit<
281
+ type SelectBaseProps<TItem, TId extends ItemId> = Omit<
317
282
  SelectPrimitive.Root.Props<string, false>,
318
283
  "children" | "onValueChange" | "value" | "defaultValue" | "items" | "multiple"
319
284
  > & {
@@ -340,7 +305,7 @@ type SelectBaseProps<TItem, TId extends SelectId> = Omit<
340
305
 
341
306
  export type SelectProps<
342
307
  TItem = unknown,
343
- TId extends SelectId = InferId<TItem>,
308
+ TId extends ItemId = InferItemId<TItem>,
344
309
  > =
345
310
  | (SelectBaseProps<TItem, TId> & {
346
311
  multiple?: false;
@@ -379,9 +344,10 @@ export type SelectProps<
379
344
  * Use `SelectRoot` + primitives directly when you need full structural
380
345
  * control beyond what `className` + `classNames` slots offer.
381
346
  */
382
- export function Select<TItem = unknown, TId extends SelectId = InferId<TItem>>(
383
- allProps: SelectProps<TItem, TId>,
384
- ): React.ReactElement {
347
+ export function Select<
348
+ TItem = unknown,
349
+ TId extends ItemId = InferItemId<TItem>,
350
+ >(allProps: SelectProps<TItem, TId>): React.ReactElement {
385
351
  const {
386
352
  items,
387
353
  getLabel: getLabelProp,
@@ -0,0 +1,2 @@
1
+ export type { InferItemId, ItemId } from "./item";
2
+ export { defaultGetId, defaultGetLabel } from "./item";
@@ -0,0 +1,51 @@
1
+ /** Identifier an item can be addressed by. */
2
+ export type ItemId = string | number;
3
+
4
+ /**
5
+ * Infers the id type from the shape of an item, mirroring `defaultGetId`'s
6
+ * lookup order (`id` before `value`, then the primitive itself). Lets the
7
+ * common case — `{ id, name }`, `{ label, value }`, or plain primitives — drive
8
+ * `value` / `onValueChange` typing with no explicit `getId`. Falls back to
9
+ * `string` for shapes `defaultGetId` can't address.
10
+ */
11
+ export type InferItemId<TItem> = TItem extends { id: infer I extends ItemId }
12
+ ? I
13
+ : TItem extends { value: infer V extends ItemId }
14
+ ? V
15
+ : TItem extends ItemId
16
+ ? TItem
17
+ : string;
18
+
19
+ /**
20
+ * Resolves the display text for an item. Defaults to `item.label`, `item.name`,
21
+ * `item.title`, or the stringified primitive. Always returns a string — labels
22
+ * are display text.
23
+ */
24
+ export function defaultGetLabel(item: unknown): string {
25
+ if (item == null) return "";
26
+ if (typeof item === "string" || typeof item === "number") return String(item);
27
+ if (typeof item === "object") {
28
+ const record = item as Record<string, unknown>;
29
+ if ("label" in record) return String(record.label);
30
+ if ("name" in record) return String(record.name);
31
+ if ("title" in record) return String(record.title);
32
+ }
33
+ return String(item);
34
+ }
35
+
36
+ /**
37
+ * Resolves the id for an item, preserving its native type. The internal Base UI
38
+ * layer keys options by string (callers wrap this in `String(...)`), so keeping
39
+ * the raw type here lets `onValueChange` hand back a numeric id as a number, not
40
+ * a string. Defaults to `item.id`, `item.value`, or the stringified primitive.
41
+ */
42
+ export function defaultGetId(item: unknown): ItemId {
43
+ if (item == null) return "";
44
+ if (typeof item === "string" || typeof item === "number") return item;
45
+ if (typeof item === "object") {
46
+ const record = item as Record<string, unknown>;
47
+ if ("id" in record) return record.id as ItemId;
48
+ if ("value" in record) return record.value as ItemId;
49
+ }
50
+ return String(item);
51
+ }