@elmeragroup/fuse 0.1.0-canary.21 → 0.1.0-canary.23
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.
- package/components/combobox/combobox.d.ts +5 -0
- package/components/combobox/combobox.d.ts.map +1 -1
- package/components/combobox/combobox.js +2 -2
- package/components/combobox/combobox.js.map +1 -1
- package/components/input/input.d.ts +2 -0
- package/components/input/input.d.ts.map +1 -1
- package/components/input/input.js +4 -2
- package/components/input/input.js.map +1 -1
- package/components/input-group/input-group.d.ts +9 -0
- package/components/input-group/input-group.d.ts.map +1 -1
- package/components/input-group/input-group.js +18 -3
- package/components/input-group/input-group.js.map +1 -1
- package/components/number-field/number-field.d.ts +7 -1
- package/components/number-field/number-field.d.ts.map +1 -1
- package/components/number-field/number-field.js +1 -1
- package/components/number-field/number-field.js.map +1 -1
- package/package.json +2 -2
|
@@ -27,6 +27,11 @@ export declare namespace ComboboxClear {
|
|
|
27
27
|
var displayName: string;
|
|
28
28
|
}
|
|
29
29
|
export type ComboboxInputProps = Omit<ComponentProps<typeof Combobox.Input>, "children" | "className"> & {
|
|
30
|
+
/**
|
|
31
|
+
* The input's id. Inside a `Field`, set `id` on `Combobox.Root` instead: the Root's id is the
|
|
32
|
+
* one `Field.Label` points at, so an id here leaves the label's `for` unmatched.
|
|
33
|
+
*/
|
|
34
|
+
id?: string;
|
|
30
35
|
/**
|
|
31
36
|
* Renders the caret trigger button in the inline-end addon.
|
|
32
37
|
* Hidden at runtime whenever a clear button is present in the group.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.d.ts","names":[],"sources":["../../../src/components/combobox/combobox.tsx"],"mappings":";;;;YAqDY,kBAAkB,iBAAiB,gDAAgD,KAC7F,eAAiB,MAAM,OAAO;wBAIhB,aAAa,iBAAiB,8CAC5C,OAAO,kBAAkB,OAAO,YAC/B;yBAFa;MAAA;;wBAqBA,mBAAmB,SAAS,sBAAsB,SAAkB,SAAS;yBAA7E;MAAA;;wBAIA,kBACd,WACA,aACG,SACF,sBAAsB,SAAkB,WAAW;yBAJtC;MAAA;;YAgBJ,qBAAqB,sBAAsB,SAAkB;;;;;EAKvE;;wBAGc,gBACd,WACA,qBACc,cACX,SACF,qBAAqB;yBALR;MAAA;;YAqBJ,qBAAqB,KAC/B,sBAAsB,SAAkB;;;;;;
|
|
1
|
+
{"version":3,"file":"combobox.d.ts","names":[],"sources":["../../../src/components/combobox/combobox.tsx"],"mappings":";;;;YAqDY,kBAAkB,iBAAiB,gDAAgD,KAC7F,eAAiB,MAAM,OAAO;wBAIhB,aAAa,iBAAiB,8CAC5C,OAAO,kBAAkB,OAAO,YAC/B;yBAFa;MAAA;;wBAqBA,mBAAmB,SAAS,sBAAsB,SAAkB,SAAS;yBAA7E;MAAA;;wBAIA,kBACd,WACA,aACG,SACF,sBAAsB,SAAkB,WAAW;yBAJtC;MAAA;;YAgBJ,qBAAqB,sBAAsB,SAAkB;;;;;EAKvE;;wBAGc,gBACd,WACA,qBACc,cACX,SACF,qBAAqB;yBALR;MAAA;;YAqBJ,qBAAqB,KAC/B,sBAAsB,SAAkB;;;;;EAOxC;;;;;;EAMA;;;;;EAKA;;;;;;EAMA;;;;EAIA;;;;EAIA,WAAW;;;;;EAKX;;;;;EAKA;;wBAGc,gBACd,WACA,UACA,UACA,aACA,WACA,YACA,iBACG,SACF,qBAAqB;yBATR;MAAA;;YAwCJ,uBAAuB,sBAAsB,SAAkB,SACzE,uBAAuB,sBAAsB,SAAkB;;;;;EAK7D,SAAS,sBAAsB,SAAkB;IAC/C;wBAEU,kBACd,WACA,MACA,YACA,OACA,aACA,QACA,cACG,SACF,uBAAuB;yBATV;MAAA;;wBAqCA,eACd,cACG,SACF,sBAAsB,SAAkB,QAAQ;yBAHnC;MAAA;;wBAgBA,eACd,WACA,aACG,SACF,sBAAsB,SAAkB,QAAQ;yBAJnC;MAAA;;wBAwBA,gBACd,cACG,SACF,sBAAsB,SAAkB,SAAS;yBAHpC;MAAA;;wBASA,gBACd,cACG,SACF,sBAAsB,SAAkB,cAAc;yBAHzC;MAAA;;wBAaA,wBACX,SACF,sBAAsB,SAAkB,cAAc;yBAFzC;MAAA;;wBAMA,gBACd,WACA,aACG,SACF,sBAAsB,SAAkB,SAAS;yBAJpC;MAAA;;wBAmBA,oBACd,cACG,SACF,sBAAsB,SAAkB,aAAa;yBAHxC;MAAA;;wBAaA,gBACd,cACG,SACF,sBAAsB,SAAkB,SAAS;yBAHpC;MAAA;;YA2BJ,oBAAoB,sBAAsB,SAAkB;;;;;EAKtE;;;;;;EAMA;;wBA6Bc,eACd,KACA,WACA,UACA,YACA,gBACG,SACF,oBAAoB;yBAPP;MAAA;;wBAiEA,qBACd,cACG,SACF,sBAAsB,SAAkB,SAAS;yBAHpC;MAAA;;;;;;wBA2BA,qBAAqB,UAAU"}
|
|
@@ -16,7 +16,7 @@ import { OverlayPortal } from "../overlay/overlay-portal.js";
|
|
|
16
16
|
import { Check } from "../../icons/generated/check.js";
|
|
17
17
|
import { isTextValueNode } from "../../internal/is-text-node.js";
|
|
18
18
|
import { compactCornerClass } from "../../styles/corner-radius.js";
|
|
19
|
-
import { InputGroupAddon, InputGroupButton,
|
|
19
|
+
import { InputGroupAddon, InputGroupButton, InputGroupPlainInput, InputGroupRoot } from "../input-group/input-group.js";
|
|
20
20
|
import { ChipIndexCommit, ChipIndexContext, createChipIndexRegistry, useChipIndex } from "./hooks/use-chip-index.js";
|
|
21
21
|
import { comboboxStrings } from "./intl/index.js";
|
|
22
22
|
import { createContext, useContext, useRef, useState } from "react";
|
|
@@ -74,7 +74,7 @@ function ComboboxInput({ className, children, disabled = false, showTrigger = tr
|
|
|
74
74
|
children: [
|
|
75
75
|
/* @__PURE__ */ jsx(Combobox.Input, {
|
|
76
76
|
disabled,
|
|
77
|
-
render: /* @__PURE__ */ jsx(
|
|
77
|
+
render: /* @__PURE__ */ jsx(InputGroupPlainInput, {}),
|
|
78
78
|
...definedProps(props)
|
|
79
79
|
}),
|
|
80
80
|
/* @__PURE__ */ jsxs(InputGroupAddon, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.js","names":["ComboboxPrimitive"],"sources":["../../../src/components/combobox/combobox.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useRef, useState } from \"react\";\nimport type { ComponentProps, ReactElement, ReactNode, RefObject } from \"react\";\n\n// Subpath import (`@base-ui/react/combobox`) type-checks but crashes at runtime with a\n// null React context. Keep the package-root import until upstream fixes it.\nimport { Combobox as ComboboxPrimitive } from \"@base-ui/react\";\nimport type { ComboboxRoot as ComboboxRootType } from \"@base-ui/react\";\n\nimport { useLocalizedStrings } from \"../../hooks/use-localized-strings\";\nimport { useMergedRefs } from \"../../hooks/use-merged-refs\";\nimport { CaretDown } from \"../../icons/generated/caret-down\";\nimport { Check } from \"../../icons/generated/check\";\nimport { X } from \"../../icons/generated/x\";\nimport { definedProps } from \"../../internal/defined-props\";\nimport { isTextValueNode } from \"../../internal/is-text-node\";\nimport { useLocale } from \"../../intl/locale-context\";\nimport { cn } from \"../../styles/cn\";\nimport { controlMd } from \"../../styles/control-size-md\";\nimport { compactCornerClass } from \"../../styles/corner-radius\";\nimport { mergeClassName } from \"../../styles/merge-class-name\";\nimport { withinStateFaceClass, withinStateFaceControlClass } from \"../../styles/state-face\";\nimport { withinFocusRingClass, withinFocusRingControlClass } from \"../../styles/utils\";\nimport { Button } from \"../button/button\";\nimport {\n InputGroupAddon,\n InputGroupButton,\n InputGroupInput,\n InputGroupRoot,\n} from \"../input-group/input-group\";\nimport {\n menuGroupLabelClass,\n menuItemClass,\n menuItemIndicatorClass,\n menuSeparatorClass,\n overlayPositionerClass,\n overlayTimedPopupClass,\n} from \"../overlay/overlay-classes\";\nimport { OverlayPortal } from \"../overlay/overlay-portal\";\nimport type { OverlayContainerProps, OverlayPositionerProps } from \"../overlay/overlay-props\";\nimport {\n ChipIndexCommit,\n ChipIndexContext,\n createChipIndexRegistry,\n useChipIndex,\n} from \"./hooks/use-chip-index\";\nimport { comboboxStrings } from \"./intl\";\n\ntype ComboboxItemLabelFn = (itemValue: ReactNode) => string;\n\nconst ComboboxItemToStringLabelContext = createContext<ComboboxItemLabelFn | undefined>(undefined);\n\nexport type ComboboxRootProps<Value = unknown, Multiple extends boolean | undefined = false> = Omit<\n ComboboxRootType.Props<Value, Multiple>,\n \"locale\"\n>;\n\nexport function ComboboxRoot<Value = unknown, Multiple extends boolean | undefined = false>(\n props: ComboboxRootProps<Value, Multiple>\n): ReactElement {\n const { locale } = useLocale();\n const itemToStringLabel = props.itemToStringLabel;\n return (\n <ComboboxItemToStringLabelContext.Provider\n value={\n itemToStringLabel === undefined\n ? undefined\n : (itemValue) => {\n // SAFETY: Chip only calls this with the selected value from this Root,\n // which is `Value`.\n return itemToStringLabel(itemValue as Value);\n }\n }>\n <ComboboxPrimitive.Root {...props} locale={locale} />\n </ComboboxItemToStringLabelContext.Provider>\n );\n}\n\nexport function ComboboxValue({ ...props }: ComponentProps<typeof ComboboxPrimitive.Value>): ReactElement {\n return <ComboboxPrimitive.Value data-slot=\"combobox-value\" {...props} />;\n}\n\nexport function ComboboxTrigger({\n className,\n children,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Trigger>): ReactElement {\n return (\n <ComboboxPrimitive.Trigger\n data-slot=\"combobox-trigger\"\n className={mergeClassName(className, \"[&_svg:not([class*='size-'])]:size-4\")}\n {...definedProps(props)}>\n {children}\n <CaretDown className=\"ease-in-out pointer-events-none size-4 text-muted-foreground transition-transform duration-200 in-data-popup-open:rotate-180\" />\n </ComboboxPrimitive.Trigger>\n );\n}\n\nexport type ComboboxClearProps = ComponentProps<typeof ComboboxPrimitive.Clear> & {\n /**\n * Accessible name for the icon-only clear button. An explicit `aria-label` wins over it;\n * both default to the locale dictionary.\n */\n label?: string;\n};\n\nexport function ComboboxClear({\n className,\n label,\n \"aria-label\": ariaLabel,\n ...props\n}: ComboboxClearProps): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n // The render element's props win in Base UI's merge, so the resolved name must live there.\n const accessibleName = ariaLabel ?? label ?? strings.format(\"clear\");\n return (\n <ComboboxPrimitive.Clear\n data-slot=\"combobox-clear\"\n render={<InputGroupButton variant=\"ghost\" size=\"icon-sm\" aria-label={accessibleName} />}\n aria-label={accessibleName}\n className={mergeClassName(className)}\n {...definedProps(props)}>\n <X className=\"pointer-events-none\" />\n </ComboboxPrimitive.Clear>\n );\n}\n\nexport type ComboboxInputProps = Omit<\n ComponentProps<typeof ComboboxPrimitive.Input>,\n \"children\" | \"className\"\n> & {\n /**\n * Renders the caret trigger button in the inline-end addon.\n * Hidden at runtime whenever a clear button is present in the group.\n * @default true\n */\n showTrigger?: boolean;\n /**\n * Renders `Combobox.Clear` in the inline-end addon.\n * @default false\n */\n showClear?: boolean;\n /**\n * Disables the input and the trigger/clear buttons. Combines with `disabled` on\n * `Combobox.Root` and `Field.Root`: any of the three disables the input.\n * @default false\n */\n disabled?: boolean;\n /**\n * Extra classes, applied to the outer InputGroup (`w-auto`), not the input element.\n */\n className?: string;\n /**\n * Rendered inside the InputGroup after the addon (extra addons, hidden inputs).\n */\n children?: ReactNode;\n /**\n * Accessible name for the auto-rendered Clear button when `showClear`. Defaults to\n * the locale dictionary.\n */\n clearLabel?: string;\n /**\n * Accessible name for the auto-rendered caret trigger when `showTrigger`. Defaults to\n * the locale dictionary.\n */\n triggerLabel?: string;\n};\n\nexport function ComboboxInput({\n className,\n children,\n disabled = false,\n showTrigger = true,\n showClear = false,\n clearLabel,\n triggerLabel,\n ...props\n}: ComboboxInputProps): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n return (\n <InputGroupRoot className={cn(\"w-auto\", className)}>\n <ComboboxPrimitive.Input disabled={disabled} render={<InputGroupInput />} {...definedProps(props)} />\n <InputGroupAddon align=\"inline-end\">\n {showTrigger ? (\n <InputGroupButton\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={triggerLabel ?? strings.format(\"toggle\")}\n // Field.Label labelledby would win over aria-label. An empty list overrides it,\n // so the caret stays dictionary `toggle`.\n aria-labelledby=\"\"\n render={<ComboboxTrigger />}\n data-slot=\"input-group-button\"\n className=\"group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent\"\n disabled={disabled}\n />\n ) : null}\n {showClear ? <ComboboxClear disabled={disabled} label={clearLabel} /> : null}\n </InputGroupAddon>\n {children}\n </InputGroupRoot>\n );\n}\n\nexport type ComboboxContentProps = ComponentProps<typeof ComboboxPrimitive.Popup> &\n OverlayPositionerProps<ComponentProps<typeof ComboboxPrimitive.Positioner>> & {\n /**\n * Element, ref, or virtual element to position against. Pass `useComboboxAnchor()`'s\n * ref; also flips `data-external-anchor` on the popup.\n */\n anchor?: ComponentProps<typeof ComboboxPrimitive.Positioner>[\"anchor\"];\n } & OverlayContainerProps;\n\nexport function ComboboxContent({\n className,\n side = \"bottom\",\n sideOffset = 6,\n align = \"start\",\n alignOffset = 0,\n anchor,\n container,\n ...props\n}: ComboboxContentProps): ReactElement | null {\n return (\n <OverlayPortal portal={ComboboxPrimitive.Portal} container={container}>\n <ComboboxPrimitive.Positioner\n side={side}\n sideOffset={sideOffset}\n align={align}\n alignOffset={alignOffset}\n anchor={anchor}\n className={overlayPositionerClass}>\n <ComboboxPrimitive.Popup\n data-slot=\"combobox-content\"\n data-external-anchor={anchor ? \"true\" : \"false\"}\n className={mergeClassName(\n className,\n overlayTimedPopupClass,\n // Popup padding insets a search group; a child margin plus the group's w-full\n // overflowed the box. Scope it to popups that directly own one, so a plain list\n // keeps the menu family's single p-1 inset instead of double-insetting.\n \"group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] overflow-hidden has-[>[data-slot=input-group]]:px-1 has-[>[data-slot=input-group]]:pt-1.5 data-[external-anchor=true]:min-w-(--anchor-width) *:data-[slot=input-group]:h-(--control-h-sm) *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none\"\n )}\n {...props}\n />\n </ComboboxPrimitive.Positioner>\n </OverlayPortal>\n );\n}\n\nexport function ComboboxList({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.List>): ReactElement {\n return (\n <ComboboxPrimitive.List\n data-slot=\"combobox-list\"\n className={mergeClassName(\n className,\n \"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0\"\n )}\n {...props}\n />\n );\n}\n\nexport function ComboboxItem({\n className,\n children,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Item>): ReactElement {\n return (\n <ComboboxPrimitive.Item\n data-slot=\"combobox-item\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- option padding is menu layout, not a control rung\n className={mergeClassName(\n className,\n menuItemClass,\n // oxlint-disable-next-line elmera/no-local-focus-ring -- the highlight face menuItemClass leaves to the family; base-ui spells it `data-highlighted:` on listbox options\n \"w-full pr-8 pl-2 data-highlighted:bg-accent data-highlighted:text-accent-foreground data-highlighted:**:text-accent-foreground\"\n )}\n {...props}>\n {children}\n <ComboboxPrimitive.ItemIndicator render={<span className={cn(menuItemIndicatorClass, \"size-4\")} />}>\n <Check className=\"pointer-events-none\" />\n </ComboboxPrimitive.ItemIndicator>\n </ComboboxPrimitive.Item>\n );\n}\n\nexport function ComboboxGroup({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Group>): ReactElement {\n return (\n <ComboboxPrimitive.Group data-slot=\"combobox-group\" className={mergeClassName(className)} {...props} />\n );\n}\n\nexport function ComboboxLabel({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.GroupLabel>): ReactElement {\n return (\n <ComboboxPrimitive.GroupLabel\n data-slot=\"combobox-label\"\n className={mergeClassName(className, menuGroupLabelClass)}\n {...props}\n />\n );\n}\n\nexport function ComboboxCollection({\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Collection>): ReactElement {\n return <ComboboxPrimitive.Collection data-slot=\"combobox-collection\" {...props} />;\n}\n\nexport function ComboboxEmpty({\n className,\n children,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Empty>): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n return (\n <ComboboxPrimitive.Empty\n data-slot=\"combobox-empty\"\n className={mergeClassName(\n className,\n \"text-sm hidden w-full justify-center py-2 text-center text-muted-foreground group-data-empty/combobox-content:flex\"\n )}\n {...props}>\n {children ?? strings.format(\"empty\")}\n </ComboboxPrimitive.Empty>\n );\n}\n\nexport function ComboboxSeparator({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Separator>): ReactElement {\n return (\n <ComboboxPrimitive.Separator\n data-slot=\"combobox-separator\"\n className={mergeClassName(className, menuSeparatorClass)}\n {...props}\n />\n );\n}\n\nexport function ComboboxChips({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Chips>): ReactElement {\n const [registry] = useState(createChipIndexRegistry);\n return (\n <ChipIndexContext.Provider value={registry}>\n <ComboboxPrimitive.Value>\n {(selected: ReactNode) => <ChipIndexCommit selected={selected} />}\n </ComboboxPrimitive.Value>\n <ComboboxPrimitive.Chips\n data-slot=\"combobox-chips\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- chip wrap gap and compact chip padding are layout, not a control rung\n className={mergeClassName(\n className,\n controlMd.minHeight(),\n controlMd.inset(),\n \"text-sm shadow-xs flex flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding py-1.5 transition-[color,box-shadow] has-data-[slot=combobox-chip]:px-1.5\",\n withinFocusRingClass,\n withinStateFaceClass\n )}\n {...props}\n />\n </ChipIndexContext.Provider>\n );\n}\n\nexport type ComboboxChipProps = ComponentProps<typeof ComboboxPrimitive.Chip> & {\n /**\n * Renders the chip-remove button with an `X`.\n * @default true\n */\n showRemove?: boolean;\n /**\n * Accessible name for the remove button. Defaults to dictionary `removeItem`\n * formatted with the chip's string children, then `itemToStringLabel(value)`.\n * When neither yields text, the localized \"Remove\" string with no trailing space.\n */\n removeLabel?: string;\n};\n\nfunction chipItemName(children: ReactNode): string {\n if (!isTextValueNode(children)) {\n return \"\";\n }\n return children.toString().trim();\n}\n\nfunction chipValueAt(selected: ReactNode, index: number): ReactNode {\n if (!Array.isArray(selected)) {\n return selected;\n }\n // SAFETY: multiple-mode `Value` yields the consumer's selected items; Chip\n // indexes that list by committed DOM order, matching Base UI removal.\n return (selected[index] ?? null) as ReactNode;\n}\n\nfunction stringifyChipValue(value: ReactNode, itemToStringLabel?: ComboboxItemLabelFn): string {\n if (value == null || value === false || value === true) {\n return \"\";\n }\n if (itemToStringLabel !== undefined) {\n return itemToStringLabel(value).trim();\n }\n return chipItemName(value);\n}\n\nexport function ComboboxChip({\n ref,\n className,\n children,\n showRemove = true,\n removeLabel,\n ...props\n}: ComboboxChipProps): ReactElement {\n const { ref: indexRef, index } = useChipIndex();\n const mergedRef = useMergedRefs(ref, indexRef);\n return (\n <ComboboxPrimitive.Chip\n ref={mergedRef}\n data-slot=\"combobox-chip\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- chip chrome is compact token, not a control rung\n className={mergeClassName(\n className,\n \"text-xs font-medium flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 whitespace-nowrap text-foreground has-data-[slot=combobox-chip-remove]:pr-0\"\n )}\n {...props}>\n {children}\n {showRemove ? (\n <ComboboxChipRemoveLabel removeLabel={removeLabel} chipChildren={children} index={index} />\n ) : null}\n </ComboboxPrimitive.Chip>\n );\n}\n\nfunction ComboboxChipRemoveLabel({\n removeLabel,\n chipChildren,\n index,\n}: {\n removeLabel?: string;\n chipChildren: ReactNode;\n index: number;\n}): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n const itemToStringLabel = useContext(ComboboxItemToStringLabelContext);\n return (\n <ComboboxPrimitive.Value>\n {(selected: ReactNode) => {\n const fromChildren = chipItemName(chipChildren);\n const item = fromChildren || stringifyChipValue(chipValueAt(selected, index), itemToStringLabel);\n const accessibleName = removeLabel ?? strings.format(\"removeItem\", { item });\n return <ComboboxChipRemove label={accessibleName} />;\n }}\n </ComboboxPrimitive.Value>\n );\n}\n\nfunction ComboboxChipRemove({ label }: { label: string }): ReactElement {\n return (\n <ComboboxPrimitive.ChipRemove\n data-slot=\"combobox-chip-remove\"\n render={<Button variant=\"ghost\" size=\"icon-sm\" aria-label={label} />}\n // The remove button sits inside a chip in the field box, so it takes the compact\n // corner instead of Button's `--radius-button`.\n className={cn(\"-ml-1 opacity-50 enabled-hover:opacity-100\", compactCornerClass)}\n aria-label={label}>\n <X className=\"pointer-events-none\" />\n </ComboboxPrimitive.ChipRemove>\n );\n}\n\nexport function ComboboxChipsInput({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Input>): ReactElement {\n return (\n <ComboboxPrimitive.Input\n data-slot=\"combobox-chip-input\"\n data-focus-ring-control=\"\"\n // The chips box keys its face off this `data-focus-ring-control` input while it is a\n // direct child. The control half keeps the `not-allowed` cursor on the input and cancels\n // its own ring, so only the chips box paints the face.\n className={mergeClassName(\n className,\n // oxlint-disable-next-line elmera/no-local-focus-ring -- native outline off; ring comes from the shared within adapter\n \"min-w-16 flex-1 outline-none\",\n withinFocusRingControlClass,\n withinStateFaceControlClass\n )}\n {...definedProps(props)}\n />\n );\n}\n\n/**\n * Typed ref helper to anchor `Combobox.Content` to an external element\n * (e.g. the `Combobox.Chips` container or an `InputGroup.Root`).\n */\nexport function useComboboxAnchor(): RefObject<HTMLDivElement | null> {\n return useRef<HTMLDivElement | null>(null);\n}\n\nComboboxRoot.displayName = \"Combobox.Root\";\nComboboxInput.displayName = \"Combobox.Input\";\nComboboxTrigger.displayName = \"Combobox.Trigger\";\nComboboxClear.displayName = \"Combobox.Clear\";\nComboboxContent.displayName = \"Combobox.Content\";\nComboboxList.displayName = \"Combobox.List\";\nComboboxItem.displayName = \"Combobox.Item\";\nComboboxGroup.displayName = \"Combobox.Group\";\nComboboxLabel.displayName = \"Combobox.Label\";\nComboboxCollection.displayName = \"Combobox.Collection\";\nComboboxEmpty.displayName = \"Combobox.Empty\";\nComboboxSeparator.displayName = \"Combobox.Separator\";\nComboboxChips.displayName = \"Combobox.Chips\";\nComboboxChip.displayName = \"Combobox.Chip\";\nComboboxChipsInput.displayName = \"Combobox.ChipsInput\";\nComboboxValue.displayName = \"Combobox.Value\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"combobox.js","names":["ComboboxPrimitive"],"sources":["../../../src/components/combobox/combobox.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useRef, useState } from \"react\";\nimport type { ComponentProps, ReactElement, ReactNode, RefObject } from \"react\";\n\n// Subpath import (`@base-ui/react/combobox`) type-checks but crashes at runtime with a\n// null React context. Keep the package-root import until upstream fixes it.\nimport { Combobox as ComboboxPrimitive } from \"@base-ui/react\";\nimport type { ComboboxRoot as ComboboxRootType } from \"@base-ui/react\";\n\nimport { useLocalizedStrings } from \"../../hooks/use-localized-strings\";\nimport { useMergedRefs } from \"../../hooks/use-merged-refs\";\nimport { CaretDown } from \"../../icons/generated/caret-down\";\nimport { Check } from \"../../icons/generated/check\";\nimport { X } from \"../../icons/generated/x\";\nimport { definedProps } from \"../../internal/defined-props\";\nimport { isTextValueNode } from \"../../internal/is-text-node\";\nimport { useLocale } from \"../../intl/locale-context\";\nimport { cn } from \"../../styles/cn\";\nimport { controlMd } from \"../../styles/control-size-md\";\nimport { compactCornerClass } from \"../../styles/corner-radius\";\nimport { mergeClassName } from \"../../styles/merge-class-name\";\nimport { withinStateFaceClass, withinStateFaceControlClass } from \"../../styles/state-face\";\nimport { withinFocusRingClass, withinFocusRingControlClass } from \"../../styles/utils\";\nimport { Button } from \"../button/button\";\nimport {\n InputGroupAddon,\n InputGroupButton,\n InputGroupPlainInput,\n InputGroupRoot,\n} from \"../input-group/input-group\";\nimport {\n menuGroupLabelClass,\n menuItemClass,\n menuItemIndicatorClass,\n menuSeparatorClass,\n overlayPositionerClass,\n overlayTimedPopupClass,\n} from \"../overlay/overlay-classes\";\nimport { OverlayPortal } from \"../overlay/overlay-portal\";\nimport type { OverlayContainerProps, OverlayPositionerProps } from \"../overlay/overlay-props\";\nimport {\n ChipIndexCommit,\n ChipIndexContext,\n createChipIndexRegistry,\n useChipIndex,\n} from \"./hooks/use-chip-index\";\nimport { comboboxStrings } from \"./intl\";\n\ntype ComboboxItemLabelFn = (itemValue: ReactNode) => string;\n\nconst ComboboxItemToStringLabelContext = createContext<ComboboxItemLabelFn | undefined>(undefined);\n\nexport type ComboboxRootProps<Value = unknown, Multiple extends boolean | undefined = false> = Omit<\n ComboboxRootType.Props<Value, Multiple>,\n \"locale\"\n>;\n\nexport function ComboboxRoot<Value = unknown, Multiple extends boolean | undefined = false>(\n props: ComboboxRootProps<Value, Multiple>\n): ReactElement {\n const { locale } = useLocale();\n const itemToStringLabel = props.itemToStringLabel;\n return (\n <ComboboxItemToStringLabelContext.Provider\n value={\n itemToStringLabel === undefined\n ? undefined\n : (itemValue) => {\n // SAFETY: Chip only calls this with the selected value from this Root,\n // which is `Value`.\n return itemToStringLabel(itemValue as Value);\n }\n }>\n <ComboboxPrimitive.Root {...props} locale={locale} />\n </ComboboxItemToStringLabelContext.Provider>\n );\n}\n\nexport function ComboboxValue({ ...props }: ComponentProps<typeof ComboboxPrimitive.Value>): ReactElement {\n return <ComboboxPrimitive.Value data-slot=\"combobox-value\" {...props} />;\n}\n\nexport function ComboboxTrigger({\n className,\n children,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Trigger>): ReactElement {\n return (\n <ComboboxPrimitive.Trigger\n data-slot=\"combobox-trigger\"\n className={mergeClassName(className, \"[&_svg:not([class*='size-'])]:size-4\")}\n {...definedProps(props)}>\n {children}\n <CaretDown className=\"ease-in-out pointer-events-none size-4 text-muted-foreground transition-transform duration-200 in-data-popup-open:rotate-180\" />\n </ComboboxPrimitive.Trigger>\n );\n}\n\nexport type ComboboxClearProps = ComponentProps<typeof ComboboxPrimitive.Clear> & {\n /**\n * Accessible name for the icon-only clear button. An explicit `aria-label` wins over it;\n * both default to the locale dictionary.\n */\n label?: string;\n};\n\nexport function ComboboxClear({\n className,\n label,\n \"aria-label\": ariaLabel,\n ...props\n}: ComboboxClearProps): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n // The render element's props win in Base UI's merge, so the resolved name must live there.\n const accessibleName = ariaLabel ?? label ?? strings.format(\"clear\");\n return (\n <ComboboxPrimitive.Clear\n data-slot=\"combobox-clear\"\n render={<InputGroupButton variant=\"ghost\" size=\"icon-sm\" aria-label={accessibleName} />}\n aria-label={accessibleName}\n className={mergeClassName(className)}\n {...definedProps(props)}>\n <X className=\"pointer-events-none\" />\n </ComboboxPrimitive.Clear>\n );\n}\n\nexport type ComboboxInputProps = Omit<\n ComponentProps<typeof ComboboxPrimitive.Input>,\n \"children\" | \"className\"\n> & {\n /**\n * The input's id. Inside a `Field`, set `id` on `Combobox.Root` instead: the Root's id is the\n * one `Field.Label` points at, so an id here leaves the label's `for` unmatched.\n */\n id?: string;\n /**\n * Renders the caret trigger button in the inline-end addon.\n * Hidden at runtime whenever a clear button is present in the group.\n * @default true\n */\n showTrigger?: boolean;\n /**\n * Renders `Combobox.Clear` in the inline-end addon.\n * @default false\n */\n showClear?: boolean;\n /**\n * Disables the input and the trigger/clear buttons. Combines with `disabled` on\n * `Combobox.Root` and `Field.Root`: any of the three disables the input.\n * @default false\n */\n disabled?: boolean;\n /**\n * Extra classes, applied to the outer InputGroup (`w-auto`), not the input element.\n */\n className?: string;\n /**\n * Rendered inside the InputGroup after the addon (extra addons, hidden inputs).\n */\n children?: ReactNode;\n /**\n * Accessible name for the auto-rendered Clear button when `showClear`. Defaults to\n * the locale dictionary.\n */\n clearLabel?: string;\n /**\n * Accessible name for the auto-rendered caret trigger when `showTrigger`. Defaults to\n * the locale dictionary.\n */\n triggerLabel?: string;\n};\n\nexport function ComboboxInput({\n className,\n children,\n disabled = false,\n showTrigger = true,\n showClear = false,\n clearLabel,\n triggerLabel,\n ...props\n}: ComboboxInputProps): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n return (\n <InputGroupRoot className={cn(\"w-auto\", className)}>\n <ComboboxPrimitive.Input\n disabled={disabled}\n render={<InputGroupPlainInput />}\n {...definedProps(props)}\n />\n <InputGroupAddon align=\"inline-end\">\n {showTrigger ? (\n <InputGroupButton\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={triggerLabel ?? strings.format(\"toggle\")}\n // Field.Label labelledby would win over aria-label. An empty list overrides it,\n // so the caret stays dictionary `toggle`.\n aria-labelledby=\"\"\n render={<ComboboxTrigger />}\n data-slot=\"input-group-button\"\n className=\"group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent\"\n disabled={disabled}\n />\n ) : null}\n {showClear ? <ComboboxClear disabled={disabled} label={clearLabel} /> : null}\n </InputGroupAddon>\n {children}\n </InputGroupRoot>\n );\n}\n\nexport type ComboboxContentProps = ComponentProps<typeof ComboboxPrimitive.Popup> &\n OverlayPositionerProps<ComponentProps<typeof ComboboxPrimitive.Positioner>> & {\n /**\n * Element, ref, or virtual element to position against. Pass `useComboboxAnchor()`'s\n * ref; also flips `data-external-anchor` on the popup.\n */\n anchor?: ComponentProps<typeof ComboboxPrimitive.Positioner>[\"anchor\"];\n } & OverlayContainerProps;\n\nexport function ComboboxContent({\n className,\n side = \"bottom\",\n sideOffset = 6,\n align = \"start\",\n alignOffset = 0,\n anchor,\n container,\n ...props\n}: ComboboxContentProps): ReactElement | null {\n return (\n <OverlayPortal portal={ComboboxPrimitive.Portal} container={container}>\n <ComboboxPrimitive.Positioner\n side={side}\n sideOffset={sideOffset}\n align={align}\n alignOffset={alignOffset}\n anchor={anchor}\n className={overlayPositionerClass}>\n <ComboboxPrimitive.Popup\n data-slot=\"combobox-content\"\n data-external-anchor={anchor ? \"true\" : \"false\"}\n className={mergeClassName(\n className,\n overlayTimedPopupClass,\n // Popup padding insets a search group; a child margin plus the group's w-full\n // overflowed the box. Scope it to popups that directly own one, so a plain list\n // keeps the menu family's single p-1 inset instead of double-insetting.\n \"group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] overflow-hidden has-[>[data-slot=input-group]]:px-1 has-[>[data-slot=input-group]]:pt-1.5 data-[external-anchor=true]:min-w-(--anchor-width) *:data-[slot=input-group]:h-(--control-h-sm) *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none\"\n )}\n {...props}\n />\n </ComboboxPrimitive.Positioner>\n </OverlayPortal>\n );\n}\n\nexport function ComboboxList({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.List>): ReactElement {\n return (\n <ComboboxPrimitive.List\n data-slot=\"combobox-list\"\n className={mergeClassName(\n className,\n \"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0\"\n )}\n {...props}\n />\n );\n}\n\nexport function ComboboxItem({\n className,\n children,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Item>): ReactElement {\n return (\n <ComboboxPrimitive.Item\n data-slot=\"combobox-item\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- option padding is menu layout, not a control rung\n className={mergeClassName(\n className,\n menuItemClass,\n // oxlint-disable-next-line elmera/no-local-focus-ring -- the highlight face menuItemClass leaves to the family; base-ui spells it `data-highlighted:` on listbox options\n \"w-full pr-8 pl-2 data-highlighted:bg-accent data-highlighted:text-accent-foreground data-highlighted:**:text-accent-foreground\"\n )}\n {...props}>\n {children}\n <ComboboxPrimitive.ItemIndicator render={<span className={cn(menuItemIndicatorClass, \"size-4\")} />}>\n <Check className=\"pointer-events-none\" />\n </ComboboxPrimitive.ItemIndicator>\n </ComboboxPrimitive.Item>\n );\n}\n\nexport function ComboboxGroup({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Group>): ReactElement {\n return (\n <ComboboxPrimitive.Group data-slot=\"combobox-group\" className={mergeClassName(className)} {...props} />\n );\n}\n\nexport function ComboboxLabel({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.GroupLabel>): ReactElement {\n return (\n <ComboboxPrimitive.GroupLabel\n data-slot=\"combobox-label\"\n className={mergeClassName(className, menuGroupLabelClass)}\n {...props}\n />\n );\n}\n\nexport function ComboboxCollection({\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Collection>): ReactElement {\n return <ComboboxPrimitive.Collection data-slot=\"combobox-collection\" {...props} />;\n}\n\nexport function ComboboxEmpty({\n className,\n children,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Empty>): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n return (\n <ComboboxPrimitive.Empty\n data-slot=\"combobox-empty\"\n className={mergeClassName(\n className,\n \"text-sm hidden w-full justify-center py-2 text-center text-muted-foreground group-data-empty/combobox-content:flex\"\n )}\n {...props}>\n {children ?? strings.format(\"empty\")}\n </ComboboxPrimitive.Empty>\n );\n}\n\nexport function ComboboxSeparator({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Separator>): ReactElement {\n return (\n <ComboboxPrimitive.Separator\n data-slot=\"combobox-separator\"\n className={mergeClassName(className, menuSeparatorClass)}\n {...props}\n />\n );\n}\n\nexport function ComboboxChips({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Chips>): ReactElement {\n const [registry] = useState(createChipIndexRegistry);\n return (\n <ChipIndexContext.Provider value={registry}>\n <ComboboxPrimitive.Value>\n {(selected: ReactNode) => <ChipIndexCommit selected={selected} />}\n </ComboboxPrimitive.Value>\n <ComboboxPrimitive.Chips\n data-slot=\"combobox-chips\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- chip wrap gap and compact chip padding are layout, not a control rung\n className={mergeClassName(\n className,\n controlMd.minHeight(),\n controlMd.inset(),\n \"text-sm shadow-xs flex flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent bg-clip-padding py-1.5 transition-[color,box-shadow] has-data-[slot=combobox-chip]:px-1.5\",\n withinFocusRingClass,\n withinStateFaceClass\n )}\n {...props}\n />\n </ChipIndexContext.Provider>\n );\n}\n\nexport type ComboboxChipProps = ComponentProps<typeof ComboboxPrimitive.Chip> & {\n /**\n * Renders the chip-remove button with an `X`.\n * @default true\n */\n showRemove?: boolean;\n /**\n * Accessible name for the remove button. Defaults to dictionary `removeItem`\n * formatted with the chip's string children, then `itemToStringLabel(value)`.\n * When neither yields text, the localized \"Remove\" string with no trailing space.\n */\n removeLabel?: string;\n};\n\nfunction chipItemName(children: ReactNode): string {\n if (!isTextValueNode(children)) {\n return \"\";\n }\n return children.toString().trim();\n}\n\nfunction chipValueAt(selected: ReactNode, index: number): ReactNode {\n if (!Array.isArray(selected)) {\n return selected;\n }\n // SAFETY: multiple-mode `Value` yields the consumer's selected items; Chip\n // indexes that list by committed DOM order, matching Base UI removal.\n return (selected[index] ?? null) as ReactNode;\n}\n\nfunction stringifyChipValue(value: ReactNode, itemToStringLabel?: ComboboxItemLabelFn): string {\n if (value == null || value === false || value === true) {\n return \"\";\n }\n if (itemToStringLabel !== undefined) {\n return itemToStringLabel(value).trim();\n }\n return chipItemName(value);\n}\n\nexport function ComboboxChip({\n ref,\n className,\n children,\n showRemove = true,\n removeLabel,\n ...props\n}: ComboboxChipProps): ReactElement {\n const { ref: indexRef, index } = useChipIndex();\n const mergedRef = useMergedRefs(ref, indexRef);\n return (\n <ComboboxPrimitive.Chip\n ref={mergedRef}\n data-slot=\"combobox-chip\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- chip chrome is compact token, not a control rung\n className={mergeClassName(\n className,\n \"text-xs font-medium flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 whitespace-nowrap text-foreground has-data-[slot=combobox-chip-remove]:pr-0\"\n )}\n {...props}>\n {children}\n {showRemove ? (\n <ComboboxChipRemoveLabel removeLabel={removeLabel} chipChildren={children} index={index} />\n ) : null}\n </ComboboxPrimitive.Chip>\n );\n}\n\nfunction ComboboxChipRemoveLabel({\n removeLabel,\n chipChildren,\n index,\n}: {\n removeLabel?: string;\n chipChildren: ReactNode;\n index: number;\n}): ReactElement {\n const strings = useLocalizedStrings(comboboxStrings);\n const itemToStringLabel = useContext(ComboboxItemToStringLabelContext);\n return (\n <ComboboxPrimitive.Value>\n {(selected: ReactNode) => {\n const fromChildren = chipItemName(chipChildren);\n const item = fromChildren || stringifyChipValue(chipValueAt(selected, index), itemToStringLabel);\n const accessibleName = removeLabel ?? strings.format(\"removeItem\", { item });\n return <ComboboxChipRemove label={accessibleName} />;\n }}\n </ComboboxPrimitive.Value>\n );\n}\n\nfunction ComboboxChipRemove({ label }: { label: string }): ReactElement {\n return (\n <ComboboxPrimitive.ChipRemove\n data-slot=\"combobox-chip-remove\"\n render={<Button variant=\"ghost\" size=\"icon-sm\" aria-label={label} />}\n // The remove button sits inside a chip in the field box, so it takes the compact\n // corner instead of Button's `--radius-button`.\n className={cn(\"-ml-1 opacity-50 enabled-hover:opacity-100\", compactCornerClass)}\n aria-label={label}>\n <X className=\"pointer-events-none\" />\n </ComboboxPrimitive.ChipRemove>\n );\n}\n\nexport function ComboboxChipsInput({\n className,\n ...props\n}: ComponentProps<typeof ComboboxPrimitive.Input>): ReactElement {\n return (\n <ComboboxPrimitive.Input\n data-slot=\"combobox-chip-input\"\n data-focus-ring-control=\"\"\n // The chips box keys its face off this `data-focus-ring-control` input while it is a\n // direct child. The control half keeps the `not-allowed` cursor on the input and cancels\n // its own ring, so only the chips box paints the face.\n className={mergeClassName(\n className,\n // oxlint-disable-next-line elmera/no-local-focus-ring -- native outline off; ring comes from the shared within adapter\n \"min-w-16 flex-1 outline-none\",\n withinFocusRingControlClass,\n withinStateFaceControlClass\n )}\n {...definedProps(props)}\n />\n );\n}\n\n/**\n * Typed ref helper to anchor `Combobox.Content` to an external element\n * (e.g. the `Combobox.Chips` container or an `InputGroup.Root`).\n */\nexport function useComboboxAnchor(): RefObject<HTMLDivElement | null> {\n return useRef<HTMLDivElement | null>(null);\n}\n\nComboboxRoot.displayName = \"Combobox.Root\";\nComboboxInput.displayName = \"Combobox.Input\";\nComboboxTrigger.displayName = \"Combobox.Trigger\";\nComboboxClear.displayName = \"Combobox.Clear\";\nComboboxContent.displayName = \"Combobox.Content\";\nComboboxList.displayName = \"Combobox.List\";\nComboboxItem.displayName = \"Combobox.Item\";\nComboboxGroup.displayName = \"Combobox.Group\";\nComboboxLabel.displayName = \"Combobox.Label\";\nComboboxCollection.displayName = \"Combobox.Collection\";\nComboboxEmpty.displayName = \"Combobox.Empty\";\nComboboxSeparator.displayName = \"Combobox.Separator\";\nComboboxChips.displayName = \"Combobox.Chips\";\nComboboxChip.displayName = \"Combobox.Chip\";\nComboboxChipsInput.displayName = \"Combobox.ChipsInput\";\nComboboxValue.displayName = \"Combobox.Value\";\n"],"mappings":"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"}
|
|
@@ -16,6 +16,8 @@ export type InputProps = ComponentProps<"input"> & {
|
|
|
16
16
|
*/
|
|
17
17
|
filter?: "numeric";
|
|
18
18
|
};
|
|
19
|
+
/** Input's field box and file-input classes, for an element that paints Input's look. */
|
|
20
|
+
export declare const inputClass: string;
|
|
19
21
|
export declare function Input({ className, type, filter, inputMode, onChange, ref, ...props }: InputProps): ReactElement;
|
|
20
22
|
export declare namespace Input {
|
|
21
23
|
var displayName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","names":[],"sources":["../../../src/components/input/input.tsx"],"mappings":";;YAaY,aAAa;;;;;;;;;;;;;;EAcvB
|
|
1
|
+
{"version":3,"file":"input.d.ts","names":[],"sources":["../../../src/components/input/input.tsx"],"mappings":";;YAaY,aAAa;;;;;;;;;;;;;;EAcvB;;;qBA6EW;wBAKG,QACd,WACA,MACA,QACA,WACA,UACA,QACG,SACF,aAAa;yBARA;MAAA"}
|
|
@@ -60,6 +60,8 @@ function warnIfNotNumeric(prop, value) {
|
|
|
60
60
|
if (!isThemeDevelopment()) return;
|
|
61
61
|
if (value !== void 0 && value !== "" && stripNonDigits(String(value)) !== String(value)) console.warn(`Input: ${prop} is not a number`);
|
|
62
62
|
}
|
|
63
|
+
/** Input's field box and file-input classes, for an element that paints Input's look. */
|
|
64
|
+
const inputClass = cn(fieldBox(), "file:text-sm file:font-medium min-w-0 file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-foreground");
|
|
63
65
|
function Input({ className, type, filter, inputMode, onChange, ref, ...props }) {
|
|
64
66
|
const isNumeric = filter === "numeric";
|
|
65
67
|
const inputRef = useMergedRefs(ref, isNumeric ? listenForMixedInsertions : null);
|
|
@@ -81,7 +83,7 @@ function Input({ className, type, filter, inputMode, onChange, ref, ...props })
|
|
|
81
83
|
return /* @__PURE__ */ jsx(Input$1, {
|
|
82
84
|
type,
|
|
83
85
|
"data-slot": "input",
|
|
84
|
-
className: cn(
|
|
86
|
+
className: cn(inputClass, className),
|
|
85
87
|
...definedProps({
|
|
86
88
|
...props,
|
|
87
89
|
ref: inputRef,
|
|
@@ -92,6 +94,6 @@ function Input({ className, type, filter, inputMode, onChange, ref, ...props })
|
|
|
92
94
|
}
|
|
93
95
|
Input.displayName = "Input";
|
|
94
96
|
//#endregion
|
|
95
|
-
export { Input };
|
|
97
|
+
export { Input, inputClass };
|
|
96
98
|
|
|
97
99
|
//# sourceMappingURL=input.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","names":["InputPrimitive"],"sources":["../../../src/components/input/input.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport type { ChangeEvent, ComponentProps, ReactElement } from \"react\";\n\nimport { Input as InputPrimitive } from \"@base-ui/react/input\";\n\nimport { useMergedRefs } from \"../../hooks/use-merged-refs\";\nimport { definedProps } from \"../../internal/defined-props\";\nimport { cn } from \"../../styles/cn\";\nimport { fieldBox } from \"../../styles/field-box\";\nimport { isThemeDevelopment } from \"../../theme/validate-theme\";\n\nexport type InputProps = ComponentProps<\"input\"> & {\n /**\n * Digits-only guard. Non-digit characters are stripped from every path — typing, paste,\n * autofill — before `onChange` and Field validation read the value.\n * `maxLength` counts digits where the filter runs first: a cancelable `beforeinput` on an input\n * type with a selection API (`text`, `tel`, `search`, `url`, `password`), so a pasted\n * `912 34 567` fits `maxLength={8}` whole. Other paths keep the browser's own length handling\n * and are stripped afterwards: `type=\"email\"`, or autofill that sends no cancelable\n * `beforeinput`, is cut to `maxLength` first. The filter is meant for text-like types:\n * under `type=\"number\"` it also strips the sign and the decimal separator.\n * Sets `inputMode=\"numeric\"` unless the caller passes `inputMode` explicitly. An\n * uncontrolled numeric input restores its `defaultValue` on native form reset without\n * calling `onChange`.\n */\n filter?: \"numeric\";\n};\n\n/** One normalizer for the warning guards and the change handler, so they cannot drift. */\nfunction stripNonDigits(value: string): string {\n return value.replace(/\\D/g, \"\");\n}\n\n/**\n * Under the numeric filter, an insertion that mixes digits with other characters, such as a\n * pasted `912 34 567`, is replaced by its digits before the browser applies `maxlength`, so the\n * limit counts digits rather than separators. The digits that fit land at the caret, and the\n * caret ends after them. An insertion of digits only, or of no digits at all, keeps the native\n * path: `maxlength` still blocks a full field, and the change handler strips the rest. So do a\n * read-only or disabled input, to which Chromium still sends `beforeinput` before it refuses the\n * edit, an input type without a selection API, such as `email`, where `setRangeText` throws, and\n * an insertion another listener has already cancelled. A host veto has to run before this\n * listener, in the capture phase or on the input before it mounts: once this listener has run,\n * the event is cancelled and the digits are in.\n */\nfunction insertDigitsOnly(event: InputEvent): void {\n const input = event.currentTarget;\n if (\n !(input instanceof HTMLInputElement) ||\n !event.cancelable ||\n event.defaultPrevented ||\n !event.inputType.startsWith(\"insert\")\n ) {\n return;\n }\n const start = input.selectionStart;\n const end = input.selectionEnd;\n if (input.readOnly || input.disabled || start === null || end === null) {\n return;\n }\n const inserted = event.data ?? event.dataTransfer?.getData(\"text/plain\") ?? \"\";\n const digits = stripNonDigits(inserted);\n if (digits === \"\" || digits === inserted) {\n return;\n }\n event.preventDefault();\n const room = input.maxLength < 0 ? digits.length : input.maxLength - input.value.length + (end - start);\n const accepted = digits.slice(0, Math.max(0, room));\n if (accepted === \"\") {\n return;\n }\n input.setRangeText(accepted, start, end, \"end\");\n // setRangeText fires no input event; React and Base UI read the edit from this one.\n input.dispatchEvent(\n new InputEvent(\"input\", { bubbles: true, composed: true, inputType: event.inputType, data: accepted })\n );\n}\n\n/**\n * Attaches {@link insertDigitsOnly} while the numeric filter is on. `source-contracts.test.ts`\n * lists this module among the reviewed listener owners.\n */\nfunction listenForMixedInsertions(input: HTMLInputElement | null): (() => void) | undefined {\n if (!input) {\n return undefined;\n }\n input.addEventListener(\"beforeinput\", insertDigitsOnly);\n return () => {\n input.removeEventListener(\"beforeinput\", insertDigitsOnly);\n };\n}\n\nfunction warnIfNotNumeric(prop: \"value\" | \"defaultValue\", value: InputProps[\"value\"]): void {\n if (!isThemeDevelopment()) {\n return;\n }\n if (value !== undefined && value !== \"\" && stripNonDigits(String(value)) !== String(value)) {\n console.warn(`Input: ${prop} is not a number`);\n }\n}\n\nexport function Input({\n className,\n type,\n filter,\n inputMode,\n onChange,\n ref,\n ...props\n}: InputProps): ReactElement {\n const isNumeric = filter === \"numeric\";\n const inputRef = useMergedRefs(ref, isNumeric ? listenForMixedInsertions : null);\n\n useEffect(() => {\n if (!isNumeric) {\n return;\n }\n warnIfNotNumeric(\"defaultValue\", props.defaultValue);\n warnIfNotNumeric(\"value\", props.value);\n }, [isNumeric, props.defaultValue, props.value]);\n\n function handleChange(event: ChangeEvent<HTMLInputElement>): void {\n const next = event.currentTarget.value;\n const digits = stripNonDigits(next);\n if (digits !== next) {\n // Hand the digits back to the input before any reader sees the event, so an uncontrolled\n // input drops the rejected characters even when no `onChange` re-renders it, and Base UI's\n // own change handler, which runs after this one, validates the digits.\n event.currentTarget.value = digits;\n }\n onChange?.(event);\n }\n\n return (\n <InputPrimitive\n type={type}\n data-slot=\"input\"\n className={cn(
|
|
1
|
+
{"version":3,"file":"input.js","names":["InputPrimitive"],"sources":["../../../src/components/input/input.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport type { ChangeEvent, ComponentProps, ReactElement } from \"react\";\n\nimport { Input as InputPrimitive } from \"@base-ui/react/input\";\n\nimport { useMergedRefs } from \"../../hooks/use-merged-refs\";\nimport { definedProps } from \"../../internal/defined-props\";\nimport { cn } from \"../../styles/cn\";\nimport { fieldBox } from \"../../styles/field-box\";\nimport { isThemeDevelopment } from \"../../theme/validate-theme\";\n\nexport type InputProps = ComponentProps<\"input\"> & {\n /**\n * Digits-only guard. Non-digit characters are stripped from every path — typing, paste,\n * autofill — before `onChange` and Field validation read the value.\n * `maxLength` counts digits where the filter runs first: a cancelable `beforeinput` on an input\n * type with a selection API (`text`, `tel`, `search`, `url`, `password`), so a pasted\n * `912 34 567` fits `maxLength={8}` whole. Other paths keep the browser's own length handling\n * and are stripped afterwards: `type=\"email\"`, or autofill that sends no cancelable\n * `beforeinput`, is cut to `maxLength` first. The filter is meant for text-like types:\n * under `type=\"number\"` it also strips the sign and the decimal separator.\n * Sets `inputMode=\"numeric\"` unless the caller passes `inputMode` explicitly. An\n * uncontrolled numeric input restores its `defaultValue` on native form reset without\n * calling `onChange`.\n */\n filter?: \"numeric\";\n};\n\n/** One normalizer for the warning guards and the change handler, so they cannot drift. */\nfunction stripNonDigits(value: string): string {\n return value.replace(/\\D/g, \"\");\n}\n\n/**\n * Under the numeric filter, an insertion that mixes digits with other characters, such as a\n * pasted `912 34 567`, is replaced by its digits before the browser applies `maxlength`, so the\n * limit counts digits rather than separators. The digits that fit land at the caret, and the\n * caret ends after them. An insertion of digits only, or of no digits at all, keeps the native\n * path: `maxlength` still blocks a full field, and the change handler strips the rest. So do a\n * read-only or disabled input, to which Chromium still sends `beforeinput` before it refuses the\n * edit, an input type without a selection API, such as `email`, where `setRangeText` throws, and\n * an insertion another listener has already cancelled. A host veto has to run before this\n * listener, in the capture phase or on the input before it mounts: once this listener has run,\n * the event is cancelled and the digits are in.\n */\nfunction insertDigitsOnly(event: InputEvent): void {\n const input = event.currentTarget;\n if (\n !(input instanceof HTMLInputElement) ||\n !event.cancelable ||\n event.defaultPrevented ||\n !event.inputType.startsWith(\"insert\")\n ) {\n return;\n }\n const start = input.selectionStart;\n const end = input.selectionEnd;\n if (input.readOnly || input.disabled || start === null || end === null) {\n return;\n }\n const inserted = event.data ?? event.dataTransfer?.getData(\"text/plain\") ?? \"\";\n const digits = stripNonDigits(inserted);\n if (digits === \"\" || digits === inserted) {\n return;\n }\n event.preventDefault();\n const room = input.maxLength < 0 ? digits.length : input.maxLength - input.value.length + (end - start);\n const accepted = digits.slice(0, Math.max(0, room));\n if (accepted === \"\") {\n return;\n }\n input.setRangeText(accepted, start, end, \"end\");\n // setRangeText fires no input event; React and Base UI read the edit from this one.\n input.dispatchEvent(\n new InputEvent(\"input\", { bubbles: true, composed: true, inputType: event.inputType, data: accepted })\n );\n}\n\n/**\n * Attaches {@link insertDigitsOnly} while the numeric filter is on. `source-contracts.test.ts`\n * lists this module among the reviewed listener owners.\n */\nfunction listenForMixedInsertions(input: HTMLInputElement | null): (() => void) | undefined {\n if (!input) {\n return undefined;\n }\n input.addEventListener(\"beforeinput\", insertDigitsOnly);\n return () => {\n input.removeEventListener(\"beforeinput\", insertDigitsOnly);\n };\n}\n\nfunction warnIfNotNumeric(prop: \"value\" | \"defaultValue\", value: InputProps[\"value\"]): void {\n if (!isThemeDevelopment()) {\n return;\n }\n if (value !== undefined && value !== \"\" && stripNonDigits(String(value)) !== String(value)) {\n console.warn(`Input: ${prop} is not a number`);\n }\n}\n\n/** Input's field box and file-input classes, for an element that paints Input's look. */\nexport const inputClass = cn(\n fieldBox(),\n \"file:text-sm file:font-medium min-w-0 file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-foreground\"\n);\n\nexport function Input({\n className,\n type,\n filter,\n inputMode,\n onChange,\n ref,\n ...props\n}: InputProps): ReactElement {\n const isNumeric = filter === \"numeric\";\n const inputRef = useMergedRefs(ref, isNumeric ? listenForMixedInsertions : null);\n\n useEffect(() => {\n if (!isNumeric) {\n return;\n }\n warnIfNotNumeric(\"defaultValue\", props.defaultValue);\n warnIfNotNumeric(\"value\", props.value);\n }, [isNumeric, props.defaultValue, props.value]);\n\n function handleChange(event: ChangeEvent<HTMLInputElement>): void {\n const next = event.currentTarget.value;\n const digits = stripNonDigits(next);\n if (digits !== next) {\n // Hand the digits back to the input before any reader sees the event, so an uncontrolled\n // input drops the rejected characters even when no `onChange` re-renders it, and Base UI's\n // own change handler, which runs after this one, validates the digits.\n event.currentTarget.value = digits;\n }\n onChange?.(event);\n }\n\n return (\n <InputPrimitive\n type={type}\n data-slot=\"input\"\n className={cn(inputClass, className)}\n {...definedProps({\n ...props,\n ref: inputRef,\n inputMode: inputMode ?? (isNumeric ? \"numeric\" : undefined),\n onChange: isNumeric ? handleChange : onChange,\n })}\n />\n );\n}\n\nInput.displayName = \"Input\";\n"],"mappings":";;;;;;;;;;;AA+BA,SAAS,eAAe,OAAuB;CAC7C,OAAO,MAAM,QAAQ,OAAO,EAAE;AAChC;;;;;;;;;;;;;AAcA,SAAS,iBAAiB,OAAyB;CACjD,MAAM,QAAQ,MAAM;CACpB,IACE,EAAE,iBAAiB,qBACnB,CAAC,MAAM,cACP,MAAM,oBACN,CAAC,MAAM,UAAU,WAAW,QAAQ,GAEpC;CAEF,MAAM,QAAQ,MAAM;CACpB,MAAM,MAAM,MAAM;CAClB,IAAI,MAAM,YAAY,MAAM,YAAY,UAAU,QAAQ,QAAQ,MAChE;CAEF,MAAM,WAAW,MAAM,QAAQ,MAAM,cAAc,QAAQ,YAAY,KAAK;CAC5E,MAAM,SAAS,eAAe,QAAQ;CACtC,IAAI,WAAW,MAAM,WAAW,UAC9B;CAEF,MAAM,eAAe;CACrB,MAAM,OAAO,MAAM,YAAY,IAAI,OAAO,SAAS,MAAM,YAAY,MAAM,MAAM,UAAU,MAAM;CACjG,MAAM,WAAW,OAAO,MAAM,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;CAClD,IAAI,aAAa,IACf;CAEF,MAAM,aAAa,UAAU,OAAO,KAAK,KAAK;CAE9C,MAAM,cACJ,IAAI,WAAW,SAAS;EAAE,SAAS;EAAM,UAAU;EAAM,WAAW,MAAM;EAAW,MAAM;CAAS,CAAC,CACvG;AACF;;;;;AAMA,SAAS,yBAAyB,OAA0D;CAC1F,IAAI,CAAC,OACH;CAEF,MAAM,iBAAiB,eAAe,gBAAgB;CACtD,aAAa;EACX,MAAM,oBAAoB,eAAe,gBAAgB;CAC3D;AACF;AAEA,SAAS,iBAAiB,MAAgC,OAAkC;CAC1F,IAAI,CAAC,mBAAmB,GACtB;CAEF,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,eAAe,OAAO,KAAK,CAAC,MAAM,OAAO,KAAK,GACvF,QAAQ,KAAK,UAAU,KAAK,iBAAiB;AAEjD;;AAGA,MAAa,aAAa,GACxB,SAAS,GACT,wHACF;AAEA,SAAgB,MAAM,EACpB,WACA,MACA,QACA,WACA,UACA,KACA,GAAG,SACwB;CAC3B,MAAM,YAAY,WAAW;CAC7B,MAAM,WAAW,cAAc,KAAK,YAAY,2BAA2B,IAAI;CAE/E,gBAAgB;EACd,IAAI,CAAC,WACH;EAEF,iBAAiB,gBAAgB,MAAM,YAAY;EACnD,iBAAiB,SAAS,MAAM,KAAK;CACvC,GAAG;EAAC;EAAW,MAAM;EAAc,MAAM;CAAK,CAAC;CAE/C,SAAS,aAAa,OAA4C;EAChE,MAAM,OAAO,MAAM,cAAc;EACjC,MAAM,SAAS,eAAe,IAAI;EAClC,IAAI,WAAW,MAIb,MAAM,cAAc,QAAQ;EAE9B,WAAW,KAAK;CAClB;CAEA,OACE,oBAACA,SAAD;EACQ;EACN,aAAU;EACV,WAAW,GAAG,YAAY,SAAS;EACnC,GAAI,aAAa;GACf,GAAG;GACH,KAAK;GACL,WAAW,cAAc,YAAY,YAAY,KAAA;GACjD,UAAU,YAAY,eAAe;EACvC,CAAC;CACF,CAAA;AAEL;AAEA,MAAM,cAAc"}
|
|
@@ -60,6 +60,15 @@ export declare function InputGroupInput({ className, ...props }: InputGroupInput
|
|
|
60
60
|
export declare namespace InputGroupInput {
|
|
61
61
|
var displayName: string;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* `InputGroup.Input`'s look on a plain `<input>`, for a primitive that wires the Field itself,
|
|
65
|
+
* such as Base UI's `Combobox.Input`. `InputGroup.Input` renders `Field.Control`, which would
|
|
66
|
+
* name the input after the Field and submit its text beside the primitive's own hidden input.
|
|
67
|
+
*/
|
|
68
|
+
export declare function InputGroupPlainInput({ className, ...props }: ComponentProps<"input">): ReactElement;
|
|
69
|
+
export declare namespace InputGroupPlainInput {
|
|
70
|
+
var displayName: string;
|
|
71
|
+
}
|
|
63
72
|
export declare function InputGroupTextarea({ className, ...props }: InputGroupTextareaProps): ReactElement;
|
|
64
73
|
export declare namespace InputGroupTextarea {
|
|
65
74
|
var displayName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-group.d.ts","names":[],"sources":["../../../src/components/input-group/input-group.tsx"],"mappings":";;;;;;;;;;;;YAsBY,sBAAsB;YACtB,uBAAuB,wBAAwB,oBAAoB;KAE1E,uBAAuB,YAAY,oBAAoB;KACvD,2BAA2B,QAAQ;KACnC,4BAA4B,QAAQ,sBAAsB;KAE1D,uBAAuB,KAAK,sBAAsB;;;;;EAKrD;;YAGU,yBACP;;;;;;EAMC,OAAO;MAER;;;;;EAKC,MAAM;EACN;;YAEM,sBAAsB;YACtB,uBAAuB;YACvB,0BAA0B;
|
|
1
|
+
{"version":3,"file":"input-group.d.ts","names":[],"sources":["../../../src/components/input-group/input-group.tsx"],"mappings":";;;;;;;;;;;;YAsBY,sBAAsB;YACtB,uBAAuB,wBAAwB,oBAAoB;KAE1E,uBAAuB,YAAY,oBAAoB;KACvD,2BAA2B,QAAQ;KACnC,4BAA4B,QAAQ,sBAAsB;KAE1D,uBAAuB,KAAK,sBAAsB;;;;;EAKrD;;YAGU,yBACP;;;;;;EAMC,OAAO;MAER;;;;;EAKC,MAAM;EACN;;YAEM,sBAAsB;YACtB,uBAAuB;YACvB,0BAA0B;wBAetB,iBAAiB,cAAc,SAAS,sBAAsB;yBAA9D;MAAA;;wBAMA,kBACd,WACA,OACA,YACG,SACF,uBAAuB;yBALV;MAAA;;wBAyBA,mBACd,WACA,MACA,SACA,SACG,SACF,wBAAwB;yBANX;MAAA;;wBAoBA,iBAAiB,cAAc,SAAS,sBAAsB;yBAA9D;MAAA;;wBAgBA,kBAAkB,cAAc,SAAS,uBAAuB;yBAAhE;MAAA;;;;;;;wBAgBA,uBAAuB,cAAc,SAAS,0BAA0B;yBAAxE;MAAA;;wBAWA,qBAAqB,cAAc,SAAS,0BAA0B;yBAAtE;MAAA"}
|
|
@@ -4,7 +4,7 @@ import { withinStateFaceControlClass } from "../../styles/state-face.js";
|
|
|
4
4
|
import { withinFocusRingControlClass } from "../../styles/utils.js";
|
|
5
5
|
import { Button } from "../button/button.js";
|
|
6
6
|
import { inputGroupRootClass } from "../../styles/field-box.js";
|
|
7
|
-
import { Input } from "../input/input.js";
|
|
7
|
+
import { Input, inputClass } from "../input/input.js";
|
|
8
8
|
import { Textarea } from "../textarea/textarea.js";
|
|
9
9
|
import { inputGroupAddonVariants, inputGroupButtonVariants } from "./input-group-variants.js";
|
|
10
10
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -15,6 +15,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
15
15
|
* and state-face neutralizations come from the shared within adapters, never a local literal.
|
|
16
16
|
*/
|
|
17
17
|
const CONTROL_CHROME = cn("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 disabled:bg-transparent", withinFocusRingControlClass, withinStateFaceControlClass);
|
|
18
|
+
const INPUT_CONTROL_CHROME = cn(CONTROL_CHROME, "max-h-full");
|
|
18
19
|
function InputGroupRoot({ className, ...props }) {
|
|
19
20
|
return /* @__PURE__ */ jsx("div", {
|
|
20
21
|
"data-slot": "input-group",
|
|
@@ -56,7 +57,20 @@ function InputGroupInput({ className, ...props }) {
|
|
|
56
57
|
return /* @__PURE__ */ jsx(Input, {
|
|
57
58
|
"data-slot": "input-group-control",
|
|
58
59
|
"data-focus-ring-control": "",
|
|
59
|
-
className: cn(
|
|
60
|
+
className: cn(INPUT_CONTROL_CHROME, className),
|
|
61
|
+
...props
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `InputGroup.Input`'s look on a plain `<input>`, for a primitive that wires the Field itself,
|
|
66
|
+
* such as Base UI's `Combobox.Input`. `InputGroup.Input` renders `Field.Control`, which would
|
|
67
|
+
* name the input after the Field and submit its text beside the primitive's own hidden input.
|
|
68
|
+
*/
|
|
69
|
+
function InputGroupPlainInput({ className, ...props }) {
|
|
70
|
+
return /* @__PURE__ */ jsx("input", {
|
|
71
|
+
"data-slot": "input-group-control",
|
|
72
|
+
"data-focus-ring-control": "",
|
|
73
|
+
className: cn(inputClass, INPUT_CONTROL_CHROME, className),
|
|
60
74
|
...props
|
|
61
75
|
});
|
|
62
76
|
}
|
|
@@ -73,8 +87,9 @@ InputGroupAddon.displayName = "InputGroup.Addon";
|
|
|
73
87
|
InputGroupButton.displayName = "InputGroup.Button";
|
|
74
88
|
InputGroupText.displayName = "InputGroup.Text";
|
|
75
89
|
InputGroupInput.displayName = "InputGroup.Input";
|
|
90
|
+
InputGroupPlainInput.displayName = "InputGroupPlainInput";
|
|
76
91
|
InputGroupTextarea.displayName = "InputGroup.Textarea";
|
|
77
92
|
//#endregion
|
|
78
|
-
export { InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupRoot, InputGroupText, InputGroupTextarea };
|
|
93
|
+
export { InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupPlainInput, InputGroupRoot, InputGroupText, InputGroupTextarea };
|
|
79
94
|
|
|
80
95
|
//# sourceMappingURL=input-group.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-group.js","names":[],"sources":["../../../src/components/input-group/input-group.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentProps, ReactElement } from \"react\";\n\nimport type { VariantProps } from \"tailwind-variants\";\n\nimport { cn } from \"../../styles/cn\";\nimport { inputGroupRootClass } from \"../../styles/field-box\";\nimport { withinStateFaceControlClass } from \"../../styles/state-face\";\nimport { withinFocusRingControlClass } from \"../../styles/utils\";\nimport { Button } from \"../button/button\";\nimport { Input } from \"../input/input\";\nimport type { InputProps } from \"../input/input\";\nimport { Textarea } from \"../textarea/textarea\";\nimport { inputGroupAddonVariants, inputGroupButtonVariants } from \"./input-group-variants\";\n\n/**\n * Group chrome follows the disabled and invalid state of its own input or textarea,\n * independently of addon buttons. Keep `InputGroup.Input` or `InputGroup.Textarea` a direct\n * child of the root: the group reads its state from that child only, so a field nested in an\n * addon, such as a NumberField, keeps its state to itself.\n */\nexport type InputGroupRootProps = ComponentProps<\"div\">;\nexport type InputGroupAddonProps = ComponentProps<\"div\"> & VariantProps<typeof inputGroupAddonVariants>;\n\ntype InputGroupButtonSize = NonNullable<VariantProps<typeof inputGroupButtonVariants>[\"size\"]>;\ntype IconInputGroupButtonSize = Extract<InputGroupButtonSize, `icon${string}`>;\ntype LabelInputGroupButtonSize = Exclude<InputGroupButtonSize, IconInputGroupButtonSize>;\n\ntype InputGroupButtonBase = Omit<ComponentProps<typeof Button>, \"size\" | \"type\"> & {\n /**\n * Native button type, re-typed over `Button`'s own. Defaults to `\"button\"`, so an\n * addon action never submits the surrounding form unless it asks to.\n */\n type?: \"button\" | \"submit\" | \"reset\";\n};\n\nexport type InputGroupButtonProps =\n | (InputGroupButtonBase & {\n /**\n * Local compact size axis, not Button's `size`. Labelled values; icon\n * sizes require `aria-label`.\n * @default \"xs\"\n */\n size?: LabelInputGroupButtonSize;\n })\n | (InputGroupButtonBase & {\n /**\n * Local compact size axis, not Button's `size`. Icon sizes require\n * `aria-label`.\n */\n size: IconInputGroupButtonSize;\n \"aria-label\": string;\n });\nexport type InputGroupTextProps = ComponentProps<\"span\">;\nexport type InputGroupInputProps = InputProps;\nexport type InputGroupTextareaProps = ComponentProps<\"textarea\">;\n\n/**\n * Chrome stripped off the embedded control: the Root owns border, radius, shadow, rings and\n * the disabled dim, so the control contributes nothing but its own box. The focus-visible\n * and state-face neutralizations come from the shared within adapters, never a local literal.\n */\nconst CONTROL_CHROME = cn(\n \"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 disabled:bg-transparent\",\n withinFocusRingControlClass,\n withinStateFaceControlClass\n);\n\nexport function InputGroupRoot({ className, ...props }: InputGroupRootProps): ReactElement {\n return (\n <div data-slot=\"input-group\" role=\"group\" className={cn(inputGroupRootClass, className)} {...props} />\n );\n}\n\nexport function InputGroupAddon({\n className,\n align = \"inline-start\",\n onClick,\n ...props\n}: InputGroupAddonProps): ReactElement {\n return (\n <div\n role=\"group\"\n data-slot=\"input-group-addon\"\n data-align={align}\n className={cn(inputGroupAddonVariants({ align }), className)}\n onClick={(event) => {\n // The addon is an extension of the input's hit area, except over a\n // nested button, which keeps its own click.\n if (!(event.target instanceof HTMLElement) || !event.target.closest(\"button\")) {\n event.currentTarget.parentElement?.querySelector(\"input\")?.focus();\n }\n onClick?.(event);\n }}\n {...props}\n />\n );\n}\n\nexport function InputGroupButton({\n className,\n type = \"button\",\n variant = \"ghost\",\n size = \"xs\",\n ...props\n}: InputGroupButtonProps): ReactElement {\n return (\n <Button\n type={type}\n // The local compact axis is never Button's `size` prop: it is applied\n // as extra classes and reflected to the DOM as `data-size`.\n data-size={size}\n variant={variant}\n className={cn(inputGroupButtonVariants({ size }), className)}\n {...props}\n />\n );\n}\n\nexport function InputGroupText({ className, ...props }: InputGroupTextProps): ReactElement {\n return (\n <span\n // The reference omits the slot, which breaks the\n // ButtonGroup `[data-slot]` child-selector contract.\n data-slot=\"input-group-text\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- addon text gap is chrome, not a control rung\n className={cn(\n \"text-sm flex items-center gap-2 text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport function InputGroupInput({ className, ...props }: InputGroupInputProps): ReactElement {\n return (\n <Input\n data-slot=\"input-group-control\"\n data-focus-ring-control=\"\"\n className={cn(
|
|
1
|
+
{"version":3,"file":"input-group.js","names":[],"sources":["../../../src/components/input-group/input-group.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentProps, ReactElement } from \"react\";\n\nimport type { VariantProps } from \"tailwind-variants\";\n\nimport { cn } from \"../../styles/cn\";\nimport { inputGroupRootClass } from \"../../styles/field-box\";\nimport { withinStateFaceControlClass } from \"../../styles/state-face\";\nimport { withinFocusRingControlClass } from \"../../styles/utils\";\nimport { Button } from \"../button/button\";\nimport { Input, inputClass } from \"../input/input\";\nimport type { InputProps } from \"../input/input\";\nimport { Textarea } from \"../textarea/textarea\";\nimport { inputGroupAddonVariants, inputGroupButtonVariants } from \"./input-group-variants\";\n\n/**\n * Group chrome follows the disabled and invalid state of its own input or textarea,\n * independently of addon buttons. Keep `InputGroup.Input` or `InputGroup.Textarea` a direct\n * child of the root: the group reads its state from that child only, so a field nested in an\n * addon, such as a NumberField, keeps its state to itself.\n */\nexport type InputGroupRootProps = ComponentProps<\"div\">;\nexport type InputGroupAddonProps = ComponentProps<\"div\"> & VariantProps<typeof inputGroupAddonVariants>;\n\ntype InputGroupButtonSize = NonNullable<VariantProps<typeof inputGroupButtonVariants>[\"size\"]>;\ntype IconInputGroupButtonSize = Extract<InputGroupButtonSize, `icon${string}`>;\ntype LabelInputGroupButtonSize = Exclude<InputGroupButtonSize, IconInputGroupButtonSize>;\n\ntype InputGroupButtonBase = Omit<ComponentProps<typeof Button>, \"size\" | \"type\"> & {\n /**\n * Native button type, re-typed over `Button`'s own. Defaults to `\"button\"`, so an\n * addon action never submits the surrounding form unless it asks to.\n */\n type?: \"button\" | \"submit\" | \"reset\";\n};\n\nexport type InputGroupButtonProps =\n | (InputGroupButtonBase & {\n /**\n * Local compact size axis, not Button's `size`. Labelled values; icon\n * sizes require `aria-label`.\n * @default \"xs\"\n */\n size?: LabelInputGroupButtonSize;\n })\n | (InputGroupButtonBase & {\n /**\n * Local compact size axis, not Button's `size`. Icon sizes require\n * `aria-label`.\n */\n size: IconInputGroupButtonSize;\n \"aria-label\": string;\n });\nexport type InputGroupTextProps = ComponentProps<\"span\">;\nexport type InputGroupInputProps = InputProps;\nexport type InputGroupTextareaProps = ComponentProps<\"textarea\">;\n\n/**\n * Chrome stripped off the embedded control: the Root owns border, radius, shadow, rings and\n * the disabled dim, so the control contributes nothing but its own box. The focus-visible\n * and state-face neutralizations come from the shared within adapters, never a local literal.\n */\nconst CONTROL_CHROME = cn(\n \"flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 disabled:bg-transparent\",\n withinFocusRingControlClass,\n withinStateFaceControlClass\n);\n\nconst INPUT_CONTROL_CHROME = cn(CONTROL_CHROME, \"max-h-full\");\n\nexport function InputGroupRoot({ className, ...props }: InputGroupRootProps): ReactElement {\n return (\n <div data-slot=\"input-group\" role=\"group\" className={cn(inputGroupRootClass, className)} {...props} />\n );\n}\n\nexport function InputGroupAddon({\n className,\n align = \"inline-start\",\n onClick,\n ...props\n}: InputGroupAddonProps): ReactElement {\n return (\n <div\n role=\"group\"\n data-slot=\"input-group-addon\"\n data-align={align}\n className={cn(inputGroupAddonVariants({ align }), className)}\n onClick={(event) => {\n // The addon is an extension of the input's hit area, except over a\n // nested button, which keeps its own click.\n if (!(event.target instanceof HTMLElement) || !event.target.closest(\"button\")) {\n event.currentTarget.parentElement?.querySelector(\"input\")?.focus();\n }\n onClick?.(event);\n }}\n {...props}\n />\n );\n}\n\nexport function InputGroupButton({\n className,\n type = \"button\",\n variant = \"ghost\",\n size = \"xs\",\n ...props\n}: InputGroupButtonProps): ReactElement {\n return (\n <Button\n type={type}\n // The local compact axis is never Button's `size` prop: it is applied\n // as extra classes and reflected to the DOM as `data-size`.\n data-size={size}\n variant={variant}\n className={cn(inputGroupButtonVariants({ size }), className)}\n {...props}\n />\n );\n}\n\nexport function InputGroupText({ className, ...props }: InputGroupTextProps): ReactElement {\n return (\n <span\n // The reference omits the slot, which breaks the\n // ButtonGroup `[data-slot]` child-selector contract.\n data-slot=\"input-group-text\"\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- addon text gap is chrome, not a control rung\n className={cn(\n \"text-sm flex items-center gap-2 text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport function InputGroupInput({ className, ...props }: InputGroupInputProps): ReactElement {\n return (\n <Input\n data-slot=\"input-group-control\"\n data-focus-ring-control=\"\"\n className={cn(INPUT_CONTROL_CHROME, className)}\n {...props}\n />\n );\n}\n\n/**\n * `InputGroup.Input`'s look on a plain `<input>`, for a primitive that wires the Field itself,\n * such as Base UI's `Combobox.Input`. `InputGroup.Input` renders `Field.Control`, which would\n * name the input after the Field and submit its text beside the primitive's own hidden input.\n */\nexport function InputGroupPlainInput({ className, ...props }: ComponentProps<\"input\">): ReactElement {\n return (\n <input\n data-slot=\"input-group-control\"\n data-focus-ring-control=\"\"\n className={cn(inputClass, INPUT_CONTROL_CHROME, className)}\n {...props}\n />\n );\n}\n\nexport function InputGroupTextarea({ className, ...props }: InputGroupTextareaProps): ReactElement {\n return (\n <Textarea\n data-slot=\"input-group-control\"\n data-focus-ring-control=\"\"\n className={cn(CONTROL_CHROME, \"resize-none py-2\", className)}\n {...props}\n />\n );\n}\n\nInputGroupRoot.displayName = \"InputGroup.Root\";\nInputGroupAddon.displayName = \"InputGroup.Addon\";\nInputGroupButton.displayName = \"InputGroup.Button\";\nInputGroupText.displayName = \"InputGroup.Text\";\nInputGroupInput.displayName = \"InputGroup.Input\";\nInputGroupPlainInput.displayName = \"InputGroupPlainInput\";\nInputGroupTextarea.displayName = \"InputGroup.Textarea\";\n"],"mappings":";;;;;;;;;;;;;;;;AA+DA,MAAM,iBAAiB,GACrB,0FACA,6BACA,2BACF;AAEA,MAAM,uBAAuB,GAAG,gBAAgB,YAAY;AAE5D,SAAgB,eAAe,EAAE,WAAW,GAAG,SAA4C;CACzF,OACE,oBAAC,OAAD;EAAK,aAAU;EAAc,MAAK;EAAQ,WAAW,GAAG,qBAAqB,SAAS;EAAG,GAAI;CAAQ,CAAA;AAEzG;AAEA,SAAgB,gBAAgB,EAC9B,WACA,QAAQ,gBACR,SACA,GAAG,SACkC;CACrC,OACE,oBAAC,OAAD;EACE,MAAK;EACL,aAAU;EACV,cAAY;EACZ,WAAW,GAAG,wBAAwB,EAAE,MAAM,CAAC,GAAG,SAAS;EAC3D,UAAU,UAAU;GAGlB,IAAI,EAAE,MAAM,kBAAkB,gBAAgB,CAAC,MAAM,OAAO,QAAQ,QAAQ,GAC1E,MAAM,cAAc,eAAe,cAAc,OAAO,CAAC,EAAE,MAAM;GAEnE,UAAU,KAAK;EACjB;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,iBAAiB,EAC/B,WACA,OAAO,UACP,UAAU,SACV,OAAO,MACP,GAAG,SACmC;CACtC,OACE,oBAAC,QAAD;EACQ;EAGN,aAAW;EACF;EACT,WAAW,GAAG,yBAAyB,EAAE,KAAK,CAAC,GAAG,SAAS;EAC3D,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,eAAe,EAAE,WAAW,GAAG,SAA4C;CACzF,OACE,oBAAC,QAAD;EAGE,aAAU;EAEV,WAAW,GACT,0HACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,gBAAgB,EAAE,WAAW,GAAG,SAA6C;CAC3F,OACE,oBAAC,OAAD;EACE,aAAU;EACV,2BAAwB;EACxB,WAAW,GAAG,sBAAsB,SAAS;EAC7C,GAAI;CACL,CAAA;AAEL;;;;;;AAOA,SAAgB,qBAAqB,EAAE,WAAW,GAAG,SAAgD;CACnG,OACE,oBAAC,SAAD;EACE,aAAU;EACV,2BAAwB;EACxB,WAAW,GAAG,YAAY,sBAAsB,SAAS;EACzD,GAAI;CACL,CAAA;AAEL;AAEA,SAAgB,mBAAmB,EAAE,WAAW,GAAG,SAAgD;CACjG,OACE,oBAAC,UAAD;EACE,aAAU;EACV,2BAAwB;EACxB,WAAW,GAAG,gBAAgB,oBAAoB,SAAS;EAC3D,GAAI;CACL,CAAA;AAEL;AAEA,eAAe,cAAc;AAC7B,gBAAgB,cAAc;AAC9B,iBAAiB,cAAc;AAC/B,eAAe,cAAc;AAC7B,gBAAgB,cAAc;AAC9B,qBAAqB,cAAc;AACnC,mBAAmB,cAAc"}
|
|
@@ -40,7 +40,13 @@ export type NumberFieldProps = {
|
|
|
40
40
|
minValue?: number;
|
|
41
41
|
/** Upper bound, forwarded as base-ui `max`. */
|
|
42
42
|
maxValue?: number;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Step amount for arrows and steppers. Omitted, they step by 1 and a typed value is held to no
|
|
45
|
+
* step grid. Given with `minValue`, a typed value must also land on the step grid counted from
|
|
46
|
+
* `minValue`, or native validation blocks submission, as with `<input type="number" min step>`;
|
|
47
|
+
* pass `step={1}` to accept whole numbers only. Without `minValue`, a step never constrains a
|
|
48
|
+
* typed value.
|
|
49
|
+
*/
|
|
44
50
|
step?: number;
|
|
45
51
|
/** Formatting options, forwarded as base-ui `format`. Locale comes from the provider. */
|
|
46
52
|
formatOptions?: Intl.NumberFormatOptions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-field.d.ts","names":[],"sources":["../../../src/components/number-field/number-field.tsx"],"mappings":";;YAoBY;;EAEV;;EAEA;;;;;EAKA,eAAe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA;;EAEA;;;;;EAKA,YAAY;;EAEZ;;EAEA;;EAEA
|
|
1
|
+
{"version":3,"file":"number-field.d.ts","names":[],"sources":["../../../src/components/number-field/number-field.tsx"],"mappings":";;YAoBY;;EAEV;;EAEA;;;;;EAKA,eAAe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA;;EAEA;;;;;EAKA,YAAY;;EAEZ;;EAEA;;;;;;;;EAQA;;EAEA,gBAAgB,KAAK;;EAErB;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;wBAmBc,cACd,OACA,aACA,cACA,WACA,WACA,WACA,YACA,YACA,YACA,cACA,OACA,cACA,UACA,UACA,UACA,MACA,eACA,MACA,WACA,IACA,yBACc,WACd,eACA,iBACC,mBAAmB;yBAzBN;MAAA"}
|
|
@@ -44,7 +44,7 @@ function NumberField({ label, description, errorMessage, isPending = false, isSu
|
|
|
44
44
|
onValueChange: (next) => onChange?.(next ?? NaN),
|
|
45
45
|
min: minValue,
|
|
46
46
|
max: maxValue,
|
|
47
|
-
step,
|
|
47
|
+
step: step ?? "any",
|
|
48
48
|
format: formatOptions,
|
|
49
49
|
locale,
|
|
50
50
|
disabled: isDisabled,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-field.js","names":["NumberFieldPrimitive"],"sources":["../../../src/components/number-field/number-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRef } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\n\nimport { NumberField as NumberFieldPrimitive } from \"@base-ui/react/number-field\";\n\nimport { useLocalizedStrings } from \"../../hooks/use-localized-strings\";\nimport { useResetRemount } from \"../../hooks/use-reset-remount\";\nimport { Minus } from \"../../icons/generated/minus\";\nimport { Plus } from \"../../icons/generated/plus\";\nimport { useLocale } from \"../../intl/locale-context\";\nimport { cn } from \"../../styles/cn\";\nimport { controlMdInsetTypeClass } from \"../../styles/control-size-md\";\nimport { numberFieldGroupClass } from \"../../styles/field-box\";\nimport { withinStateFaceControlClass } from \"../../styles/state-face\";\nimport { withinFocusRingControlClass } from \"../../styles/utils\";\nimport { FieldFrame } from \"../field/field-frame\";\nimport { numberFieldStrings } from \"./intl\";\n\nexport type NumberFieldProps = {\n /** Visible label, rendered as `Field.Label`. */\n label?: string;\n /** Supporting copy, rendered as `Field.Description`. */\n description?: string;\n /**\n * Error copy, rendered as `Field.Error` when truthy. Accepts any `ReactNode`. Falsy, the\n * field shows its own validation error instead, such as a native constraint message.\n */\n errorMessage?: ReactNode;\n /** Shows a spinner in the label row. Forces that row to exist even without `label`. */\n isPending?: boolean;\n /** Shows a check in the label row and wins the crossfade over `isPending`. */\n isSuccess?: boolean;\n /** Forwards `invalid` to `Field.Root` and `aria-invalid` to the group. */\n isInvalid?: boolean;\n /** Forwards `disabled` to `Field.Root` and `NumberField.Root`; the group uses `bg-muted`. */\n isDisabled?: boolean;\n /** Forwards `readOnly` to `NumberField.Root`; the group uses `bg-muted`. */\n isReadOnly?: boolean;\n /** Forwards `required` to `NumberField.Root`. */\n isRequired?: boolean;\n /** Unit suffix rendered between the input and the steppers (visual only). */\n denomination?: string;\n /**\n * Controlled value; `NaN` is the controlled empty state. `undefined` — passed or\n * omitted — is uncontrolled, as with React's own inputs.\n */\n value?: number;\n /** Uncontrolled initial value. */\n defaultValue?: number;\n /**\n * Called with the numeric value, not the event. A cleared input reports `NaN`\n * (`onChange?.(next ?? NaN)`).\n */\n onChange?: (value: number) => void;\n /** Lower bound, forwarded as base-ui `min`. */\n minValue?: number;\n /** Upper bound, forwarded as base-ui `max`. */\n maxValue?: number;\n /** Step amount for arrows and steppers. */\n step?: number;\n /** Formatting options, forwarded as base-ui `format`. Locale comes from the provider. */\n formatOptions?: Intl.NumberFormatOptions;\n /** Native `name` forwarded to the primitive (hidden input for form submission). */\n name?: string;\n /** Extra classes, merged onto the root via `cn`. */\n className?: string;\n /** Accessible name forwarded to the input for label-less usage. */\n \"aria-label\"?: string;\n /** Accessible name of the increment stepper. Defaults to the locale dictionary. */\n increaseLabel?: string;\n /** Accessible name of the decrement stepper. Defaults to the locale dictionary. */\n decreaseLabel?: string;\n /** Native `autoFocus` forwarded to the input. */\n autoFocus?: boolean;\n /** Forwarded to `NumberField.Root`. */\n id?: string;\n};\n\n// A stepper is a part of the NumberField control, not a whole control: the group's\n// within-target state face dims the disabled field once, steppers included. A stepper\n// disabled on its own at a bound keeps the part look, a muted fill and glyph with the\n// `not-allowed` cursor, and its hover fill sits behind the `enabled-hover:` gate.\n// Each stepper is a full-height column `--control-h-xs` wide, so its target meets the\n// 24px floor at both densities (stacked steppers cannot fit two 24px targets in the\n// dense control height).\nconst stepperButton = cn(\n \"box-border flex h-full w-(--control-h-xs) shrink-0 cursor-default items-center justify-center border-0 border-s bg-background p-0 text-foreground transition-colors disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground enabled-hover:bg-muted\"\n);\n\n/**\n * Labeled number field composite over Field + base-ui NumberField.\n * Client — it owns the reset remount and change handler, and reads locale\n * from the provider.\n */\nexport function NumberField({\n label,\n description,\n errorMessage,\n isPending = false,\n isSuccess = false,\n isInvalid = false,\n isDisabled = false,\n isReadOnly = false,\n isRequired = false,\n denomination,\n value,\n defaultValue,\n onChange,\n minValue,\n maxValue,\n step,\n formatOptions,\n name,\n className,\n id,\n autoFocus,\n \"aria-label\": ariaLabel,\n increaseLabel,\n decreaseLabel,\n}: NumberFieldProps): ReactElement {\n const { locale } = useLocale();\n const strings = useLocalizedStrings(numberFieldStrings);\n const numberInputRef = useRef<HTMLInputElement>(null);\n const isControlled = value !== undefined;\n // base-ui does not observe native form reset; the hook remounts the primitive instead.\n const reset = useResetRemount(numberInputRef, !isControlled);\n const controlledValue = value === undefined ? undefined : Number.isNaN(value) ? null : value;\n\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- label/control stack gap is layout, not a control rung\n const rootClassName = cn(\"gap-1\", className);\n\n return (\n <FieldFrame\n className={rootClassName}\n invalid={isInvalid}\n disabled={isDisabled}\n label={label}\n isPending={isPending}\n isSuccess={isSuccess}\n description={description}\n errorMessage={errorMessage}>\n <NumberFieldPrimitive.Root\n key={reset.key}\n name={name}\n value={controlledValue}\n defaultValue={defaultValue}\n onValueChange={(next) => onChange?.(next ?? NaN)}\n min={minValue}\n max={maxValue}\n step={step}\n format={formatOptions}\n locale={locale}\n disabled={isDisabled}\n readOnly={isReadOnly}\n required={isRequired}\n id={id}>\n <NumberFieldPrimitive.Group\n aria-invalid={isInvalid || undefined}\n className={cn(numberFieldGroupClass, {\n \"bg-muted\": isDisabled || isReadOnly,\n })}>\n <NumberFieldPrimitive.Input\n // The reset hook needs the visible control; Root's `inputRef` prop is the\n // validation input, not this one.\n ref={numberInputRef}\n aria-label={ariaLabel}\n autoFocus={autoFocus && reset.isInitialMount}\n data-focus-ring-control=\"\"\n className={cn(\n \"box-border h-full w-full min-w-0 flex-1 border-0 bg-transparent py-0 tabular-nums\",\n controlMdInsetTypeClass,\n withinFocusRingControlClass,\n withinStateFaceControlClass\n )}\n />\n {denomination ? (\n <div className=\"text-sm px-2 py-1 text-muted-foreground\">{denomination}</div>\n ) : null}\n <div className=\"flex h-full\">\n <NumberFieldPrimitive.Decrement\n className={stepperButton}\n aria-label={decreaseLabel ?? strings.format(\"decrease\")}>\n <Minus aria-hidden className=\"size-4\" />\n </NumberFieldPrimitive.Decrement>\n <NumberFieldPrimitive.Increment\n className={stepperButton}\n aria-label={increaseLabel ?? strings.format(\"increase\")}>\n <Plus aria-hidden className=\"size-4\" />\n </NumberFieldPrimitive.Increment>\n </div>\n </NumberFieldPrimitive.Group>\n </NumberFieldPrimitive.Root>\n </FieldFrame>\n );\n}\n\nNumberField.displayName = \"NumberField\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAuFA,MAAM,gBAAgB,GACpB,yQACF;;;;;;AAOA,SAAgB,YAAY,EAC1B,OACA,aACA,cACA,YAAY,OACZ,YAAY,OACZ,YAAY,OACZ,aAAa,OACb,aAAa,OACb,aAAa,OACb,cACA,OACA,cACA,UACA,UACA,UACA,MACA,eACA,MACA,WACA,IACA,WACA,cAAc,WACd,eACA,iBACiC;CACjC,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,oBAAoB,kBAAkB;CACtD,MAAM,iBAAiB,OAAyB,IAAI;CAGpD,MAAM,QAAQ,gBAAgB,gBAAgB,EAFzB,UAAU,KAAA,EAE4B;CAC3D,MAAM,kBAAkB,UAAU,KAAA,IAAY,KAAA,IAAY,OAAO,MAAM,KAAK,IAAI,OAAO;CAGvF,MAAM,gBAAgB,GAAG,SAAS,SAAS;CAE3C,OACE,oBAAC,YAAD;EACE,WAAW;EACX,SAAS;EACT,UAAU;EACH;EACI;EACA;EACE;EACC;EACd,UAAA,oBAACA,cAAqB,MAAtB;GAEQ;GACN,OAAO;GACO;GACd,gBAAgB,SAAS,WAAW,QAAQ,GAAG;GAC/C,KAAK;GACL,KAAK;GACC;GACN,QAAQ;GACA;GACR,UAAU;GACV,UAAU;GACV,UAAU;GACN;GACJ,UAAA,qBAACA,cAAqB,OAAtB;IACE,gBAAc,aAAa,KAAA;IAC3B,WAAW,GAAG,uBAAuB,EACnC,YAAY,cAAc,WAC5B,CAAC;IAJH,UAAA;KAKE,oBAACA,cAAqB,OAAtB;MAGE,KAAK;MACL,cAAY;MACZ,WAAW,aAAa,MAAM;MAC9B,2BAAwB;MACxB,WAAW,GACT,qFACA,yBACA,6BACA,2BACF;KACD,CAAA;KACA,eACC,oBAAC,OAAD;MAAK,WAAU;MAA2C,UAAA;KAAkB,CAAA,IAC1E;KACJ,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAACA,cAAqB,WAAtB;OACE,WAAW;OACX,cAAY,iBAAiB,QAAQ,OAAO,UAAU;OACtD,UAAA,oBAAC,OAAD;QAAO,eAAA;QAAY,WAAU;OAAU,CAAA;MACT,CAAA,GAChC,oBAACA,cAAqB,WAAtB;OACE,WAAW;OACX,cAAY,iBAAiB,QAAQ,OAAO,UAAU;OACtD,UAAA,oBAAC,MAAD;QAAM,eAAA;QAAY,WAAU;OAAU,CAAA;MACR,CAAA,CAC7B;;IACqB;;EACH,GAjDpB,MAAM,GAiDc;CACjB,CAAA;AAEhB;AAEA,YAAY,cAAc"}
|
|
1
|
+
{"version":3,"file":"number-field.js","names":["NumberFieldPrimitive"],"sources":["../../../src/components/number-field/number-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRef } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\n\nimport { NumberField as NumberFieldPrimitive } from \"@base-ui/react/number-field\";\n\nimport { useLocalizedStrings } from \"../../hooks/use-localized-strings\";\nimport { useResetRemount } from \"../../hooks/use-reset-remount\";\nimport { Minus } from \"../../icons/generated/minus\";\nimport { Plus } from \"../../icons/generated/plus\";\nimport { useLocale } from \"../../intl/locale-context\";\nimport { cn } from \"../../styles/cn\";\nimport { controlMdInsetTypeClass } from \"../../styles/control-size-md\";\nimport { numberFieldGroupClass } from \"../../styles/field-box\";\nimport { withinStateFaceControlClass } from \"../../styles/state-face\";\nimport { withinFocusRingControlClass } from \"../../styles/utils\";\nimport { FieldFrame } from \"../field/field-frame\";\nimport { numberFieldStrings } from \"./intl\";\n\nexport type NumberFieldProps = {\n /** Visible label, rendered as `Field.Label`. */\n label?: string;\n /** Supporting copy, rendered as `Field.Description`. */\n description?: string;\n /**\n * Error copy, rendered as `Field.Error` when truthy. Accepts any `ReactNode`. Falsy, the\n * field shows its own validation error instead, such as a native constraint message.\n */\n errorMessage?: ReactNode;\n /** Shows a spinner in the label row. Forces that row to exist even without `label`. */\n isPending?: boolean;\n /** Shows a check in the label row and wins the crossfade over `isPending`. */\n isSuccess?: boolean;\n /** Forwards `invalid` to `Field.Root` and `aria-invalid` to the group. */\n isInvalid?: boolean;\n /** Forwards `disabled` to `Field.Root` and `NumberField.Root`; the group uses `bg-muted`. */\n isDisabled?: boolean;\n /** Forwards `readOnly` to `NumberField.Root`; the group uses `bg-muted`. */\n isReadOnly?: boolean;\n /** Forwards `required` to `NumberField.Root`. */\n isRequired?: boolean;\n /** Unit suffix rendered between the input and the steppers (visual only). */\n denomination?: string;\n /**\n * Controlled value; `NaN` is the controlled empty state. `undefined` — passed or\n * omitted — is uncontrolled, as with React's own inputs.\n */\n value?: number;\n /** Uncontrolled initial value. */\n defaultValue?: number;\n /**\n * Called with the numeric value, not the event. A cleared input reports `NaN`\n * (`onChange?.(next ?? NaN)`).\n */\n onChange?: (value: number) => void;\n /** Lower bound, forwarded as base-ui `min`. */\n minValue?: number;\n /** Upper bound, forwarded as base-ui `max`. */\n maxValue?: number;\n /**\n * Step amount for arrows and steppers. Omitted, they step by 1 and a typed value is held to no\n * step grid. Given with `minValue`, a typed value must also land on the step grid counted from\n * `minValue`, or native validation blocks submission, as with `<input type=\"number\" min step>`;\n * pass `step={1}` to accept whole numbers only. Without `minValue`, a step never constrains a\n * typed value.\n */\n step?: number;\n /** Formatting options, forwarded as base-ui `format`. Locale comes from the provider. */\n formatOptions?: Intl.NumberFormatOptions;\n /** Native `name` forwarded to the primitive (hidden input for form submission). */\n name?: string;\n /** Extra classes, merged onto the root via `cn`. */\n className?: string;\n /** Accessible name forwarded to the input for label-less usage. */\n \"aria-label\"?: string;\n /** Accessible name of the increment stepper. Defaults to the locale dictionary. */\n increaseLabel?: string;\n /** Accessible name of the decrement stepper. Defaults to the locale dictionary. */\n decreaseLabel?: string;\n /** Native `autoFocus` forwarded to the input. */\n autoFocus?: boolean;\n /** Forwarded to `NumberField.Root`. */\n id?: string;\n};\n\n// A stepper is a part of the NumberField control, not a whole control: the group's\n// within-target state face dims the disabled field once, steppers included. A stepper\n// disabled on its own at a bound keeps the part look, a muted fill and glyph with the\n// `not-allowed` cursor, and its hover fill sits behind the `enabled-hover:` gate.\n// Each stepper is a full-height column `--control-h-xs` wide, so its target meets the\n// 24px floor at both densities (stacked steppers cannot fit two 24px targets in the\n// dense control height).\nconst stepperButton = cn(\n \"box-border flex h-full w-(--control-h-xs) shrink-0 cursor-default items-center justify-center border-0 border-s bg-background p-0 text-foreground transition-colors disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground enabled-hover:bg-muted\"\n);\n\n/**\n * Labeled number field composite over Field + base-ui NumberField.\n * Client — it owns the reset remount and change handler, and reads locale\n * from the provider.\n */\nexport function NumberField({\n label,\n description,\n errorMessage,\n isPending = false,\n isSuccess = false,\n isInvalid = false,\n isDisabled = false,\n isReadOnly = false,\n isRequired = false,\n denomination,\n value,\n defaultValue,\n onChange,\n minValue,\n maxValue,\n step,\n formatOptions,\n name,\n className,\n id,\n autoFocus,\n \"aria-label\": ariaLabel,\n increaseLabel,\n decreaseLabel,\n}: NumberFieldProps): ReactElement {\n const { locale } = useLocale();\n const strings = useLocalizedStrings(numberFieldStrings);\n const numberInputRef = useRef<HTMLInputElement>(null);\n const isControlled = value !== undefined;\n // base-ui does not observe native form reset; the hook remounts the primitive instead.\n const reset = useResetRemount(numberInputRef, !isControlled);\n const controlledValue = value === undefined ? undefined : Number.isNaN(value) ? null : value;\n\n // oxlint-disable-next-line elmera/no-hardcoded-density-metrics -- label/control stack gap is layout, not a control rung\n const rootClassName = cn(\"gap-1\", className);\n\n return (\n <FieldFrame\n className={rootClassName}\n invalid={isInvalid}\n disabled={isDisabled}\n label={label}\n isPending={isPending}\n isSuccess={isSuccess}\n description={description}\n errorMessage={errorMessage}>\n <NumberFieldPrimitive.Root\n key={reset.key}\n name={name}\n value={controlledValue}\n defaultValue={defaultValue}\n onValueChange={(next) => onChange?.(next ?? NaN)}\n min={minValue}\n max={maxValue}\n // base-ui defaults `step` to 1 and puts it on the hidden validation input, so with `min`\n // an omitted step would reject any typed value off the 1-step grid from `min`, such as\n // 99.5 under a min of 1. \"any\" still steps by 1 but drops that step constraint.\n step={step ?? \"any\"}\n format={formatOptions}\n locale={locale}\n disabled={isDisabled}\n readOnly={isReadOnly}\n required={isRequired}\n id={id}>\n <NumberFieldPrimitive.Group\n aria-invalid={isInvalid || undefined}\n className={cn(numberFieldGroupClass, {\n \"bg-muted\": isDisabled || isReadOnly,\n })}>\n <NumberFieldPrimitive.Input\n // The reset hook needs the visible control; Root's `inputRef` prop is the\n // validation input, not this one.\n ref={numberInputRef}\n aria-label={ariaLabel}\n autoFocus={autoFocus && reset.isInitialMount}\n data-focus-ring-control=\"\"\n className={cn(\n \"box-border h-full w-full min-w-0 flex-1 border-0 bg-transparent py-0 tabular-nums\",\n controlMdInsetTypeClass,\n withinFocusRingControlClass,\n withinStateFaceControlClass\n )}\n />\n {denomination ? (\n <div className=\"text-sm px-2 py-1 text-muted-foreground\">{denomination}</div>\n ) : null}\n <div className=\"flex h-full\">\n <NumberFieldPrimitive.Decrement\n className={stepperButton}\n aria-label={decreaseLabel ?? strings.format(\"decrease\")}>\n <Minus aria-hidden className=\"size-4\" />\n </NumberFieldPrimitive.Decrement>\n <NumberFieldPrimitive.Increment\n className={stepperButton}\n aria-label={increaseLabel ?? strings.format(\"increase\")}>\n <Plus aria-hidden className=\"size-4\" />\n </NumberFieldPrimitive.Increment>\n </div>\n </NumberFieldPrimitive.Group>\n </NumberFieldPrimitive.Root>\n </FieldFrame>\n );\n}\n\nNumberField.displayName = \"NumberField\";\n"],"mappings":";;;;;;;;;;;;;;;;;AA6FA,MAAM,gBAAgB,GACpB,yQACF;;;;;;AAOA,SAAgB,YAAY,EAC1B,OACA,aACA,cACA,YAAY,OACZ,YAAY,OACZ,YAAY,OACZ,aAAa,OACb,aAAa,OACb,aAAa,OACb,cACA,OACA,cACA,UACA,UACA,UACA,MACA,eACA,MACA,WACA,IACA,WACA,cAAc,WACd,eACA,iBACiC;CACjC,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,oBAAoB,kBAAkB;CACtD,MAAM,iBAAiB,OAAyB,IAAI;CAGpD,MAAM,QAAQ,gBAAgB,gBAAgB,EAFzB,UAAU,KAAA,EAE4B;CAC3D,MAAM,kBAAkB,UAAU,KAAA,IAAY,KAAA,IAAY,OAAO,MAAM,KAAK,IAAI,OAAO;CAGvF,MAAM,gBAAgB,GAAG,SAAS,SAAS;CAE3C,OACE,oBAAC,YAAD;EACE,WAAW;EACX,SAAS;EACT,UAAU;EACH;EACI;EACA;EACE;EACC;EACd,UAAA,oBAACA,cAAqB,MAAtB;GAEQ;GACN,OAAO;GACO;GACd,gBAAgB,SAAS,WAAW,QAAQ,GAAG;GAC/C,KAAK;GACL,KAAK;GAIL,MAAM,QAAQ;GACd,QAAQ;GACA;GACR,UAAU;GACV,UAAU;GACV,UAAU;GACN;GACJ,UAAA,qBAACA,cAAqB,OAAtB;IACE,gBAAc,aAAa,KAAA;IAC3B,WAAW,GAAG,uBAAuB,EACnC,YAAY,cAAc,WAC5B,CAAC;IAJH,UAAA;KAKE,oBAACA,cAAqB,OAAtB;MAGE,KAAK;MACL,cAAY;MACZ,WAAW,aAAa,MAAM;MAC9B,2BAAwB;MACxB,WAAW,GACT,qFACA,yBACA,6BACA,2BACF;KACD,CAAA;KACA,eACC,oBAAC,OAAD;MAAK,WAAU;MAA2C,UAAA;KAAkB,CAAA,IAC1E;KACJ,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAACA,cAAqB,WAAtB;OACE,WAAW;OACX,cAAY,iBAAiB,QAAQ,OAAO,UAAU;OACtD,UAAA,oBAAC,OAAD;QAAO,eAAA;QAAY,WAAU;OAAU,CAAA;MACT,CAAA,GAChC,oBAACA,cAAqB,WAAtB;OACE,WAAW;OACX,cAAY,iBAAiB,QAAQ,OAAO,UAAU;OACtD,UAAA,oBAAC,MAAD;QAAM,eAAA;QAAY,WAAU;OAAU,CAAA;MACR,CAAA,CAC7B;;IACqB;;EACH,GApDpB,MAAM,GAoDc;CACjB,CAAA;AAEhB;AAEA,YAAY,cAAc"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elmeragroup/fuse",
|
|
3
|
-
"version": "0.1.0-canary.
|
|
3
|
+
"version": "0.1.0-canary.23",
|
|
4
4
|
"description": "Fuse — whitelabel React components for Elmera Group's energy brands. One package, twenty theme permutations, ESM-only.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
"access": "public"
|
|
364
364
|
},
|
|
365
365
|
"elmeraRelease": {
|
|
366
|
-
"commit": "
|
|
366
|
+
"commit": "11abd3154f8cef3bb219892b8bf33fbdd24e5092",
|
|
367
367
|
"channel": "canary"
|
|
368
368
|
}
|
|
369
369
|
}
|