@keboola/design 11.4.0 → 11.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +191 -1
- package/CHANGELOG.md +358 -0
- package/COMPONENT-API.md +68 -47
- package/dist/{AIKit-DDg5DBCG.js → AIKit-CNX6aiKm.js} +208 -62
- package/dist/AIKit-CNX6aiKm.js.map +1 -0
- package/dist/{Alert-DHJFrWnS.js → Alert-Ba5b2YPq.js} +2 -2
- package/dist/{Alert-DHJFrWnS.js.map → Alert-Ba5b2YPq.js.map} +1 -1
- package/dist/{Badge-Ch26CXoP.js → Badge-D0pXdekt.js} +2 -2
- package/dist/Badge-D0pXdekt.js.map +1 -0
- package/dist/{BrandEditor-Go7w8Zj2.js → BrandEditor-CSANsxUz.js} +6 -6
- package/dist/{BrandEditor-Go7w8Zj2.js.map → BrandEditor-CSANsxUz.js.map} +1 -1
- package/dist/{BrandLogo-Dmi7jo21.js → BrandLogo-CoKBk4MI.js} +4 -4
- package/dist/BrandLogo-CoKBk4MI.js.map +1 -0
- package/dist/{Breadcrumb-uEkM6adI.js → Breadcrumb-g_xn_-Nf.js} +2 -2
- package/dist/{Breadcrumb-uEkM6adI.js.map → Breadcrumb-g_xn_-Nf.js.map} +1 -1
- package/dist/{Checkbox-fC0BuO2B.js → Checkbox-CUIn2jap.js} +2 -2
- package/dist/{Checkbox-fC0BuO2B.js.map → Checkbox-CUIn2jap.js.map} +1 -1
- package/dist/{Clipboard-ChPdiUy3.js → Clipboard-BTW6C-b_.js} +2 -2
- package/dist/{Clipboard-ChPdiUy3.js.map → Clipboard-BTW6C-b_.js.map} +1 -1
- package/dist/{ClipboardButton-D9pDjgkB.js → ClipboardButton-CwZJdRaz.js} +2 -2
- package/dist/{ClipboardButton-D9pDjgkB.js.map → ClipboardButton-CwZJdRaz.js.map} +1 -1
- package/dist/{CodeEditor-Cxy3HnKH.js → CodeEditor-C9zy7PxK.js} +6 -6
- package/dist/{CodeEditor-Cxy3HnKH.js.map → CodeEditor-C9zy7PxK.js.map} +1 -1
- package/dist/{ColorInput-Bxj-1eLT.js → ColorInput-BZV-u_94.js} +3 -3
- package/dist/{ColorInput-Bxj-1eLT.js.map → ColorInput-BZV-u_94.js.map} +1 -1
- package/dist/{Combobox-Cs_1pj2q.js → Combobox-BfIfaVMA.js} +273 -104
- package/dist/Combobox-BfIfaVMA.js.map +1 -0
- package/dist/{Command-B7ML9YMv.js → Command-Bfa1kpbt.js} +2 -2
- package/dist/{Command-B7ML9YMv.js.map → Command-Bfa1kpbt.js.map} +1 -1
- package/dist/{CopyField-BO_sh-HO.js → CopyField-DpT0Ngz1.js} +2 -2
- package/dist/{CopyField-BO_sh-HO.js.map → CopyField-DpT0Ngz1.js.map} +1 -1
- package/dist/{DateInput-ZdaMQo5i.js → DateInput-DiEd5uCL.js} +3 -3
- package/dist/{DateInput-ZdaMQo5i.js.map → DateInput-DiEd5uCL.js.map} +1 -1
- package/dist/{DateRangeInput-DkvNh_sT.js → DateRangeInput-DPfVYchl.js} +3 -3
- package/dist/{DateRangeInput-DkvNh_sT.js.map → DateRangeInput-DPfVYchl.js.map} +1 -1
- package/dist/{FieldItem-CCd9-xnp.js → FieldItem-B7XMmZSz.js} +3 -3
- package/dist/{FieldItem-CCd9-xnp.js.map → FieldItem-B7XMmZSz.js.map} +1 -1
- package/dist/{Form-k6PH-em3.js → Form-CsttTHn4.js} +7 -7
- package/dist/Form-CsttTHn4.js.map +1 -0
- package/dist/{FormGroup-DbcDtrgU.js → FormGroup-DHvTFdno.js} +3 -3
- package/dist/{FormGroup-DbcDtrgU.js.map → FormGroup-DHvTFdno.js.map} +1 -1
- package/dist/{InputGroup-BnLHY0eq.js → InputGroup-DcZRlVnN.js} +22 -14
- package/dist/InputGroup-DcZRlVnN.js.map +1 -0
- package/dist/{Kbd-CvSM3D3-.js → Kbd-Cvvt91Q8.js} +14 -4
- package/dist/Kbd-Cvvt91Q8.js.map +1 -0
- package/dist/{MermaidDiagram-WkonYErO.js → MermaidDiagram-CXG5tC6J.js} +19 -18
- package/dist/MermaidDiagram-CXG5tC6J.js.map +1 -0
- package/dist/{MultiActionButtonGroup-CxWH7yWK.js → MultiActionButtonGroup-DokHbPgM.js} +2 -2
- package/dist/{MultiActionButtonGroup-CxWH7yWK.js.map → MultiActionButtonGroup-DokHbPgM.js.map} +1 -1
- package/dist/{Navbar-B2UsolVU.js → Navbar-Liy1TPI7.js} +6 -6
- package/dist/{Navbar-B2UsolVU.js.map → Navbar-Liy1TPI7.js.map} +1 -1
- package/dist/PageHeader-Q3IpCnQd.js.map +1 -1
- package/dist/{PageToolbar-DnbFt14Q.js → PageToolbar-DExqEYGl.js} +2 -2
- package/dist/{PageToolbar-DnbFt14Q.js.map → PageToolbar-DExqEYGl.js.map} +1 -1
- package/dist/{Popconfirm-CILjmzKI.js → Popconfirm-5pBzT_HQ.js} +10 -11
- package/dist/{Popconfirm-CILjmzKI.js.map → Popconfirm-5pBzT_HQ.js.map} +1 -1
- package/dist/{RadioGroup-XFzFd1ti.js → RadioGroup-DXPbSj8P.js} +2 -2
- package/dist/{RadioGroup-XFzFd1ti.js.map → RadioGroup-DXPbSj8P.js.map} +1 -1
- package/dist/{Search-4Bxtt8hS.js → Search-5uQAiNIP.js} +2 -2
- package/dist/{Search-4Bxtt8hS.js.map → Search-5uQAiNIP.js.map} +1 -1
- package/dist/{SearchInput-f_oY9T4Z.js → SearchInput-D4UgAXJK.js} +3 -3
- package/dist/{SearchInput-f_oY9T4Z.js.map → SearchInput-D4UgAXJK.js.map} +1 -1
- package/dist/{SectionNav-BpJlisbT.js → SectionNav-OkKCjSA-.js} +3 -3
- package/dist/{SectionNav-BpJlisbT.js.map → SectionNav-OkKCjSA-.js.map} +1 -1
- package/dist/{Select-DpcgKqKB.js → Select-DV3Hak_l.js} +3 -4
- package/dist/Select-DV3Hak_l.js.map +1 -0
- package/dist/{Sidebar-Cm6I4dE1.js → Sidebar-D3cY1kha.js} +44 -20
- package/dist/Sidebar-D3cY1kha.js.map +1 -0
- package/dist/{Slider-pPC_3gz9.js → Slider-CRf-bB4y.js} +2 -2
- package/dist/{Slider-pPC_3gz9.js.map → Slider-CRf-bB4y.js.map} +1 -1
- package/dist/{Switch-ChfPKcTE.js → Switch-B0WdGSjz.js} +7 -7
- package/dist/{Switch-ChfPKcTE.js.map → Switch-B0WdGSjz.js.map} +1 -1
- package/dist/{TextInput-DHM6dHmn.js → TextInput-DH5SkbsE.js} +3 -3
- package/dist/{TextInput-DHM6dHmn.js.map → TextInput-DH5SkbsE.js.map} +1 -1
- package/dist/{ThemeProvider-DrQJtzpd.js → ThemeProvider-2RMdVGxO.js} +17 -12
- package/dist/{ThemeProvider-DrQJtzpd.js.map → ThemeProvider-2RMdVGxO.js.map} +1 -1
- package/dist/{Timeline-6btyJl4D.js → Timeline-BG5Xogxh.js} +11 -11
- package/dist/{Timeline-6btyJl4D.js.map → Timeline-BG5Xogxh.js.map} +1 -1
- package/dist/{Workbench-BzE-Vvd9.js → Workbench-UR2FBC7I.js} +3 -1
- package/dist/Workbench-UR2FBC7I.js.map +1 -0
- package/dist/{ZoomableSvg-BWnDkR61.js → ZoomableSvg-pooPb1Cu.js} +3 -3
- package/dist/{ZoomableSvg-BWnDkR61.js.map → ZoomableSvg-pooPb1Cu.js.map} +1 -1
- package/dist/editors.js +4 -4
- package/dist/flint.js +134 -8
- package/dist/flint.js.map +1 -1
- package/dist/index.d.ts +537 -135
- package/dist/index.js +40 -39
- package/dist/internal/blocks/AIKit.js +2 -2
- package/dist/internal/blocks/AuthLayout.js +118 -0
- package/dist/internal/blocks/AuthLayout.js.map +1 -0
- package/dist/internal/blocks/BrandEditor.js +1 -1
- package/dist/internal/blocks/ClipboardButton.js +1 -1
- package/dist/internal/blocks/ColorInput.js +1 -1
- package/dist/internal/blocks/Command.js +1 -1
- package/dist/internal/blocks/CopyField.js +1 -1
- package/dist/internal/blocks/DateInput.js +1 -1
- package/dist/internal/blocks/DateRangeInput.js +1 -1
- package/dist/internal/blocks/ExportMenu.js +1 -1
- package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
- package/dist/internal/blocks/Form.js +1 -1
- package/dist/internal/blocks/FormGroup.js +1 -1
- package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
- package/dist/internal/blocks/MultiCombobox.js +107 -48
- package/dist/internal/blocks/MultiCombobox.js.map +1 -1
- package/dist/internal/blocks/NumberInput.js +1 -1
- package/dist/internal/blocks/PageToolbar.js +1 -1
- package/dist/internal/blocks/PasswordInput.js +2 -2
- package/dist/internal/blocks/SearchInput.js +1 -1
- package/dist/internal/blocks/SearchInputWithNavigation.js +2 -2
- package/dist/internal/blocks/SectionNav.js +1 -1
- package/dist/internal/blocks/TextInput.js +1 -1
- package/dist/internal/blocks/Textarea.js +1 -1
- package/dist/internal/blocks/TimeInput.js +2 -2
- package/dist/internal/components/Alert.js +1 -1
- package/dist/internal/components/Avatar.js +1 -1
- package/dist/internal/components/Badge.js +1 -1
- package/dist/internal/components/BrandLogo.js +1 -1
- package/dist/internal/components/Breadcrumb.js +1 -1
- package/dist/internal/components/Checkbox.js +1 -1
- package/dist/internal/components/Clipboard.js +1 -1
- package/dist/internal/components/CollapsiblePanel.js +1 -1
- package/dist/internal/components/Combobox.js +2 -2
- package/dist/internal/components/InputGroup.js +1 -1
- package/dist/internal/components/Kbd.js +1 -1
- package/dist/internal/components/Navbar.js +1 -1
- package/dist/internal/components/Popconfirm.js +1 -1
- package/dist/internal/components/RadioGroup.js +1 -1
- package/dist/internal/components/Search.js +1 -1
- package/dist/internal/components/Select.js +1 -1
- package/dist/internal/components/Sidebar.js +1 -1
- package/dist/internal/components/Slider.js +1 -1
- package/dist/internal/components/Switch.js +1 -1
- package/dist/internal/components/ThemeProvider.js +1 -1
- package/dist/internal/components/Timeline.js +1 -1
- package/dist/internal/components/Tooltip.js +1 -1
- package/dist/internal/components/Truncated.js +1 -1
- package/dist/internal/components/Workbench.js +1 -1
- package/dist/internal/components/ZoomableSvg.js +1 -1
- package/dist/internal/utils.js +2 -2
- package/dist/markdown-editor.js +29 -23
- package/dist/markdown-editor.js.map +1 -1
- package/dist/mermaid.js +1 -1
- package/dist/rjsf.js +22 -23
- package/dist/rjsf.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing.js +1 -1
- package/dist/{useColorSchemeState-klihG9-i.js → themeScopeContext-D7fSyb06.js} +16 -3
- package/dist/themeScopeContext-D7fSyb06.js.map +1 -0
- package/dist/{themeTokens-D70HeNVF.js → themeTokens-qP1-55O7.js} +37 -10
- package/dist/themeTokens-qP1-55O7.js.map +1 -0
- package/dist/{utils-B5Z52hYs.js → utils-DSvrhU8E.js} +6 -2
- package/dist/{utils-B5Z52hYs.js.map → utils-DSvrhU8E.js.map} +1 -1
- package/dist/vega.d.ts +1 -1
- package/dist/vega.js +3 -3
- package/dist/vega.js.map +1 -1
- package/dist/{vegaTheme-DfoEQhOy.js → vegaTheme-CSvFEMBl.js} +6 -6
- package/dist/vegaTheme-CSvFEMBl.js.map +1 -0
- package/package.json +12 -10
- package/src/styles.css +3 -0
- package/dist/AIKit-DDg5DBCG.js.map +0 -1
- package/dist/Badge-Ch26CXoP.js.map +0 -1
- package/dist/BrandLogo-Dmi7jo21.js.map +0 -1
- package/dist/Combobox-Cs_1pj2q.js.map +0 -1
- package/dist/Form-k6PH-em3.js.map +0 -1
- package/dist/InputGroup-BnLHY0eq.js.map +0 -1
- package/dist/Kbd-CvSM3D3-.js.map +0 -1
- package/dist/MermaidDiagram-WkonYErO.js.map +0 -1
- package/dist/Select-DpcgKqKB.js.map +0 -1
- package/dist/Sidebar-Cm6I4dE1.js.map +0 -1
- package/dist/Workbench-BzE-Vvd9.js.map +0 -1
- package/dist/themeTokens-D70HeNVF.js.map +0 -1
- package/dist/useColorSchemeState-klihG9-i.js.map +0 -1
- package/dist/vegaTheme-DfoEQhOy.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ColorInput-Bxj-1eLT.js","names":[],"sources":["../src/blocks/ColorInput/utils.ts","../src/blocks/ColorInput/ColorPicker.tsx","../src/blocks/ColorInput/ColorInput.tsx"],"sourcesContent":["import type { ComponentProps } from 'react';\n\nconst HEX_CHARSET = /^[0-9a-fA-F]+$/;\n// `#` + 3, 4, 6, or 8 hex digits — every length CSS accepts as a hex color.\nconst COMPLETE_HEX_PATTERN = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n// Longest hex body CSS accepts (`#rrggbbaa` without the hash).\nconst MAX_HEX_BODY_LENGTH = 8;\n// Native <input type=\"color\"> default when fed anything unparseable.\nconst PICKER_FALLBACK = '#000000';\n\nconst extractHexBody = (value: unknown): string | null => {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n if (trimmed === '') return null;\n return trimmed.startsWith('#') ? trimmed.slice(1) : trimmed;\n};\n\nexport const isCompleteHexColor = (value: string): boolean => COMPLETE_HEX_PATTERN.test(value);\n\n// @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/showPicker\nexport const showPicker = (input: HTMLInputElement) => {\n const isShowPickerSupported = 'showPicker' in HTMLInputElement.prototype;\n isShowPickerSupported ? input.showPicker() : input.click();\n};\n\n/**\n * Coerces free-form text into a partial hex color for the visible text\n * input. Allows up to 8 hex digits to support `#rrggbbaa` (RGBA / alpha).\n *\n * Behaviour:\n * - empty / null / undefined / non-string → `''`\n * - trims surrounding whitespace\n * - prepends `#` when the user types raw hex without it\n * - keeps only the leading run of valid hex digits, dropping the rest\n * - caps total length at `# + 8` hex digits\n * - returns `''` when no valid hex digits remain (e.g. bare `#`, `zz`) so\n * the input doesn't sit on a lonely `#`\n *\n * Note: the result may be a *prefix* (e.g. `#a`, `#ab`) that is not yet a\n * valid CSS color — gate any rendering on `isCompleteHexColor`.\n */\nexport const coerceHexInputValue = (value?: string | null): string => {\n const body = extractHexBody(value);\n if (body === null) return '';\n const [validBody] = body.match(/^[0-9a-fA-F]+/) ?? [];\n if (!validBody) return '';\n return `#${validBody.slice(0, MAX_HEX_BODY_LENGTH)}`;\n};\n\n/**\n * Coerces any value into the strict lowercase `#rrggbb` form required by the\n * native `<input type=\"color\">`. Anything the picker can't render is\n * collapsed to `#000000`.\n *\n * - 3-digit shorthand (`#abc`) → expanded to 6 digits (`#aabbcc`)\n * - 4/8-digit RGBA → alpha channel is dropped\n * - invalid characters, wrong length, or non-string types → `#000000`\n *\n * The native picker silently lowercases its value; we lowercase here too so\n * controlled consumers see the same string round-trip.\n */\nexport const coerceColorPickerValue = (value: ComponentProps<'input'>['value'] | null) => {\n const body = extractHexBody(value);\n if (body === null || !HEX_CHARSET.test(body)) return PICKER_FALLBACK;\n\n if (body.length === 3 || body.length === 4) {\n const [r, g, b] = body;\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n if (body.length === 6 || body.length === 8) {\n return `#${body.slice(0, 6).toLowerCase()}`;\n }\n return PICKER_FALLBACK;\n};\n","import { type ComponentProps, type ComponentRef, useRef } from 'react';\n\nimport { InputGroup } from '../../components/InputGroup';\nimport { Pipette } from '../../icons';\n\nimport { coerceColorPickerValue, showPicker } from './utils';\n\ntype Props = Omit<ComponentProps<'input'>, 'type'>;\n\nexport const ColorPicker = ({ value, disabled, readOnly, ...props }: Props) => {\n const pickerRef = useRef<ComponentRef<'input'>>(null);\n\n const handleOpenPicker = () => {\n if (!pickerRef.current) return;\n showPicker(pickerRef.current);\n };\n\n return (\n <div className=\"tw:relative\">\n <input\n {...props}\n ref={pickerRef}\n type=\"color\"\n value={coerceColorPickerValue(value)}\n tabIndex={-1}\n aria-hidden\n className=\"tw:pointer-events-none tw:absolute tw:left-0 tw:top-full tw:size-0 tw:opacity-0\"\n disabled={disabled}\n readOnly={readOnly}\n />\n <InputGroup.Button\n onMouseDown={(event) => event.preventDefault()}\n disabled={disabled || readOnly}\n onClick={handleOpenPicker}\n aria-label=\"Pick color\"\n >\n <Pipette className=\"tw:size-4\" />\n </InputGroup.Button>\n </div>\n );\n};\n","import { useRef } from 'react';\nimport type {\n ChangeEventHandler,\n ComponentProps,\n ComponentRef,\n FocusEventHandler,\n MouseEventHandler,\n} from 'react';\n\nimport { InputGroup } from '../../components/InputGroup';\nimport { useControllableState } from '../../hooks/useControllableState';\nimport { X } from '../../icons';\nimport { cn, createChangeInputEvent, mergeRefs } from '../../utils';\n\nimport { ColorPicker } from './ColorPicker';\nimport { coerceHexInputValue, isCompleteHexColor } from './utils';\n\ntype InputGroupProps = Pick<\n ComponentProps<typeof InputGroup>,\n 'variant' | 'focusVariant' | 'state' | 'sizing'\n>;\n\ntype InputGroupInputProps = Omit<ComponentProps<typeof InputGroup.Input>, 'value' | 'defaultValue'>;\n\ntype Props = InputGroupProps &\n InputGroupInputProps & {\n value?: string | null;\n defaultValue?: string | null;\n onValueChange?: (value: string) => void;\n onValueBlur?: (value: string) => void;\n allowClear?: boolean;\n };\n\nexport const ColorInput = ({\n ref,\n\n // group props\n state,\n variant,\n focusVariant,\n sizing,\n className,\n\n // own props\n value: valueProp,\n onValueChange,\n onValueBlur,\n onChange,\n onBlur,\n defaultValue,\n allowClear = false,\n\n // input props\n disabled,\n readOnly,\n ...restInputProps\n}: Props) => {\n const inputRef = useRef<ComponentRef<'input'>>(null);\n\n const [value, setValue] = useControllableState<string>({\n // A `null` controlled value stays controlled (coerced to ''); `undefined` is uncontrolled.\n prop: valueProp === null ? '' : valueProp,\n defaultProp: defaultValue ?? '',\n onChange: onValueChange,\n });\n\n const isControlled = valueProp !== undefined;\n\n const canClear = allowClear && !!value && !disabled && !readOnly;\n const swatchColor = coerceHexInputValue(value);\n const isValidSwatch = isCompleteHexColor(swatchColor);\n\n // Preserve raw text while the user is typing — sanitise on blur.\n const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {\n setValue(event.target.value);\n onChange?.(event);\n };\n\n const handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {\n const newValue = event.target.value;\n const sanitizedNewValue = coerceHexInputValue(newValue);\n\n if (sanitizedNewValue !== newValue) {\n // In controlled mode, let the parent decide whether to apply the\n // sanitised value via `onValueChange` — don't fight its `value` prop.\n if (!isControlled) event.target.value = sanitizedNewValue;\n setValue(sanitizedNewValue);\n }\n\n onValueBlur?.(sanitizedNewValue);\n onBlur?.(event);\n };\n\n const handlePickerChange: ChangeEventHandler<HTMLInputElement> = (event) => {\n setValue(coerceHexInputValue(event.target.value));\n onChange?.(event);\n };\n\n const handleClear: MouseEventHandler<HTMLButtonElement> = () => {\n if (!inputRef.current) return;\n inputRef.current.value = '';\n setValue('');\n onChange?.(createChangeInputEvent(inputRef.current));\n };\n\n return (\n <InputGroup\n state={state}\n variant={variant}\n sizing={sizing}\n focusVariant={focusVariant}\n className={className}\n >\n <InputGroup.Addon\n align=\"inline-start\"\n className={cn(\n { 'tw:-ml-7 tw:opacity-0': !value },\n 'tw:transition-[opacity,margin]',\n 'tw:ease-linear',\n 'tw:duration-200',\n )}\n >\n <div\n style={isValidSwatch ? { background: swatchColor } : undefined}\n className={cn(\n 'tw:size-5',\n 'tw:shrink-0',\n 'tw:rounded-sm',\n 'tw:border',\n 'tw:border-solid',\n 'tw:border-neutral-300',\n {\n 'tw:bg-[linear-gradient(to_top_right,transparent_calc(50%_-_1px),theme(colors.red.300)_calc(50%_-_1px),theme(colors.red.300)_calc(50%_+_1px),transparent_calc(50%_+_1px))]':\n !isValidSwatch,\n },\n )}\n />\n </InputGroup.Addon>\n\n <InputGroup.Input\n {...restInputProps}\n type=\"text\"\n ref={mergeRefs(ref ?? null, inputRef)}\n value={value}\n onChange={handleChange}\n onBlur={handleBlur}\n disabled={disabled}\n readOnly={readOnly}\n />\n\n <InputGroup.Addon align=\"inline-end\">\n {canClear && (\n <InputGroup.Button\n // prevents input focus stealing\n onMouseDown={(event) => event.preventDefault()}\n onClick={handleClear}\n aria-label=\"Clear color\"\n >\n <X className=\"tw:size-4\" />\n </InputGroup.Button>\n )}\n <ColorPicker\n disabled={disabled}\n readOnly={readOnly}\n value={value}\n onChange={handlePickerChange}\n />\n </InputGroup.Addon>\n </InputGroup>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ColorInput-BZV-u_94.js","names":[],"sources":["../src/blocks/ColorInput/utils.ts","../src/blocks/ColorInput/ColorPicker.tsx","../src/blocks/ColorInput/ColorInput.tsx"],"sourcesContent":["import type { ComponentProps } from 'react';\n\nconst HEX_CHARSET = /^[0-9a-fA-F]+$/;\n// `#` + 3, 4, 6, or 8 hex digits — every length CSS accepts as a hex color.\nconst COMPLETE_HEX_PATTERN = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n// Longest hex body CSS accepts (`#rrggbbaa` without the hash).\nconst MAX_HEX_BODY_LENGTH = 8;\n// Native <input type=\"color\"> default when fed anything unparseable.\nconst PICKER_FALLBACK = '#000000';\n\nconst extractHexBody = (value: unknown): string | null => {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n if (trimmed === '') return null;\n return trimmed.startsWith('#') ? trimmed.slice(1) : trimmed;\n};\n\nexport const isCompleteHexColor = (value: string): boolean => COMPLETE_HEX_PATTERN.test(value);\n\n// @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/showPicker\nexport const showPicker = (input: HTMLInputElement) => {\n const isShowPickerSupported = 'showPicker' in HTMLInputElement.prototype;\n isShowPickerSupported ? input.showPicker() : input.click();\n};\n\n/**\n * Coerces free-form text into a partial hex color for the visible text\n * input. Allows up to 8 hex digits to support `#rrggbbaa` (RGBA / alpha).\n *\n * Behaviour:\n * - empty / null / undefined / non-string → `''`\n * - trims surrounding whitespace\n * - prepends `#` when the user types raw hex without it\n * - keeps only the leading run of valid hex digits, dropping the rest\n * - caps total length at `# + 8` hex digits\n * - returns `''` when no valid hex digits remain (e.g. bare `#`, `zz`) so\n * the input doesn't sit on a lonely `#`\n *\n * Note: the result may be a *prefix* (e.g. `#a`, `#ab`) that is not yet a\n * valid CSS color — gate any rendering on `isCompleteHexColor`.\n */\nexport const coerceHexInputValue = (value?: string | null): string => {\n const body = extractHexBody(value);\n if (body === null) return '';\n const [validBody] = body.match(/^[0-9a-fA-F]+/) ?? [];\n if (!validBody) return '';\n return `#${validBody.slice(0, MAX_HEX_BODY_LENGTH)}`;\n};\n\n/**\n * Coerces any value into the strict lowercase `#rrggbb` form required by the\n * native `<input type=\"color\">`. Anything the picker can't render is\n * collapsed to `#000000`.\n *\n * - 3-digit shorthand (`#abc`) → expanded to 6 digits (`#aabbcc`)\n * - 4/8-digit RGBA → alpha channel is dropped\n * - invalid characters, wrong length, or non-string types → `#000000`\n *\n * The native picker silently lowercases its value; we lowercase here too so\n * controlled consumers see the same string round-trip.\n */\nexport const coerceColorPickerValue = (value: ComponentProps<'input'>['value'] | null) => {\n const body = extractHexBody(value);\n if (body === null || !HEX_CHARSET.test(body)) return PICKER_FALLBACK;\n\n if (body.length === 3 || body.length === 4) {\n const [r, g, b] = body;\n return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();\n }\n if (body.length === 6 || body.length === 8) {\n return `#${body.slice(0, 6).toLowerCase()}`;\n }\n return PICKER_FALLBACK;\n};\n","import { type ComponentProps, type ComponentRef, useRef } from 'react';\n\nimport { InputGroup } from '../../components/InputGroup';\nimport { Pipette } from '../../icons';\n\nimport { coerceColorPickerValue, showPicker } from './utils';\n\ntype Props = Omit<ComponentProps<'input'>, 'type'>;\n\nexport const ColorPicker = ({ value, disabled, readOnly, ...props }: Props) => {\n const pickerRef = useRef<ComponentRef<'input'>>(null);\n\n const handleOpenPicker = () => {\n if (!pickerRef.current) return;\n showPicker(pickerRef.current);\n };\n\n return (\n <div className=\"tw:relative\">\n <input\n {...props}\n ref={pickerRef}\n type=\"color\"\n value={coerceColorPickerValue(value)}\n tabIndex={-1}\n aria-hidden\n className=\"tw:pointer-events-none tw:absolute tw:left-0 tw:top-full tw:size-0 tw:opacity-0\"\n disabled={disabled}\n readOnly={readOnly}\n />\n <InputGroup.Button\n onMouseDown={(event) => event.preventDefault()}\n disabled={disabled || readOnly}\n onClick={handleOpenPicker}\n aria-label=\"Pick color\"\n >\n <Pipette className=\"tw:size-4\" />\n </InputGroup.Button>\n </div>\n );\n};\n","import { useRef } from 'react';\nimport type {\n ChangeEventHandler,\n ComponentProps,\n ComponentRef,\n FocusEventHandler,\n MouseEventHandler,\n} from 'react';\n\nimport { InputGroup } from '../../components/InputGroup';\nimport { useControllableState } from '../../hooks/useControllableState';\nimport { X } from '../../icons';\nimport { cn, createChangeInputEvent, mergeRefs } from '../../utils';\n\nimport { ColorPicker } from './ColorPicker';\nimport { coerceHexInputValue, isCompleteHexColor } from './utils';\n\ntype InputGroupProps = Pick<\n ComponentProps<typeof InputGroup>,\n 'variant' | 'focusVariant' | 'state' | 'sizing'\n>;\n\ntype InputGroupInputProps = Omit<ComponentProps<typeof InputGroup.Input>, 'value' | 'defaultValue'>;\n\ntype Props = InputGroupProps &\n InputGroupInputProps & {\n value?: string | null;\n defaultValue?: string | null;\n onValueChange?: (value: string) => void;\n onValueBlur?: (value: string) => void;\n allowClear?: boolean;\n };\n\nexport const ColorInput = ({\n ref,\n\n // group props\n state,\n variant,\n focusVariant,\n sizing,\n className,\n\n // own props\n value: valueProp,\n onValueChange,\n onValueBlur,\n onChange,\n onBlur,\n defaultValue,\n allowClear = false,\n\n // input props\n disabled,\n readOnly,\n ...restInputProps\n}: Props) => {\n const inputRef = useRef<ComponentRef<'input'>>(null);\n\n const [value, setValue] = useControllableState<string>({\n // A `null` controlled value stays controlled (coerced to ''); `undefined` is uncontrolled.\n prop: valueProp === null ? '' : valueProp,\n defaultProp: defaultValue ?? '',\n onChange: onValueChange,\n });\n\n const isControlled = valueProp !== undefined;\n\n const canClear = allowClear && !!value && !disabled && !readOnly;\n const swatchColor = coerceHexInputValue(value);\n const isValidSwatch = isCompleteHexColor(swatchColor);\n\n // Preserve raw text while the user is typing — sanitise on blur.\n const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {\n setValue(event.target.value);\n onChange?.(event);\n };\n\n const handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {\n const newValue = event.target.value;\n const sanitizedNewValue = coerceHexInputValue(newValue);\n\n if (sanitizedNewValue !== newValue) {\n // In controlled mode, let the parent decide whether to apply the\n // sanitised value via `onValueChange` — don't fight its `value` prop.\n if (!isControlled) event.target.value = sanitizedNewValue;\n setValue(sanitizedNewValue);\n }\n\n onValueBlur?.(sanitizedNewValue);\n onBlur?.(event);\n };\n\n const handlePickerChange: ChangeEventHandler<HTMLInputElement> = (event) => {\n setValue(coerceHexInputValue(event.target.value));\n onChange?.(event);\n };\n\n const handleClear: MouseEventHandler<HTMLButtonElement> = () => {\n if (!inputRef.current) return;\n inputRef.current.value = '';\n setValue('');\n onChange?.(createChangeInputEvent(inputRef.current));\n };\n\n return (\n <InputGroup\n state={state}\n variant={variant}\n sizing={sizing}\n focusVariant={focusVariant}\n className={className}\n >\n <InputGroup.Addon\n align=\"inline-start\"\n className={cn(\n { 'tw:-ml-7 tw:opacity-0': !value },\n 'tw:transition-[opacity,margin]',\n 'tw:ease-linear',\n 'tw:duration-200',\n )}\n >\n <div\n style={isValidSwatch ? { background: swatchColor } : undefined}\n className={cn(\n 'tw:size-5',\n 'tw:shrink-0',\n 'tw:rounded-sm',\n 'tw:border',\n 'tw:border-solid',\n 'tw:border-neutral-300',\n {\n 'tw:bg-[linear-gradient(to_top_right,transparent_calc(50%_-_1px),theme(colors.red.300)_calc(50%_-_1px),theme(colors.red.300)_calc(50%_+_1px),transparent_calc(50%_+_1px))]':\n !isValidSwatch,\n },\n )}\n />\n </InputGroup.Addon>\n\n <InputGroup.Input\n {...restInputProps}\n type=\"text\"\n ref={mergeRefs(ref ?? null, inputRef)}\n value={value}\n onChange={handleChange}\n onBlur={handleBlur}\n disabled={disabled}\n readOnly={readOnly}\n />\n\n <InputGroup.Addon align=\"inline-end\">\n {canClear && (\n <InputGroup.Button\n // prevents input focus stealing\n onMouseDown={(event) => event.preventDefault()}\n onClick={handleClear}\n aria-label=\"Clear color\"\n >\n <X className=\"tw:size-4\" />\n </InputGroup.Button>\n )}\n <ColorPicker\n disabled={disabled}\n readOnly={readOnly}\n value={value}\n onChange={handlePickerChange}\n />\n </InputGroup.Addon>\n </InputGroup>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,18 +1,97 @@
|
|
|
1
1
|
import { cn } from "./classNames-BzrkDnOD.js";
|
|
2
|
+
import { noop } from "./utils-DSvrhU8E.js";
|
|
2
3
|
import { useArgoDesign } from "./context-D7X470dy.js";
|
|
3
4
|
import { Check, ChevronDown, Plus, X } from "./icons-Cek151jC.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { useStableCallback } from "./internal/hooks/useStableCallback.js";
|
|
6
|
+
import { InputGroup } from "./InputGroup-DcZRlVnN.js";
|
|
7
|
+
import { Badge } from "./Badge-D0pXdekt.js";
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
7
9
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
10
|
import { Combobox } from "@base-ui/react/combobox";
|
|
9
11
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
12
|
+
import { DismissableLayer } from "@radix-ui/react-dismissable-layer";
|
|
13
|
+
//#region src/components/Combobox/blankValues.ts
|
|
14
|
+
/**
|
|
15
|
+
* The two blank strings a Storage filter predicates on, in list order.
|
|
16
|
+
*
|
|
17
|
+
* - Reach for {@link withBlankValues} to offer them; this is the pair itself.
|
|
18
|
+
*/
|
|
19
|
+
const BLANK_VALUES = ["", " "];
|
|
20
|
+
const BLANK_LABELS = {
|
|
21
|
+
"": "[empty string]",
|
|
22
|
+
" ": "[space character]"
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* A string as a row or a chip shows it — named when its text is blank, itself otherwise.
|
|
26
|
+
*
|
|
27
|
+
* - Exported so an `itemToLabel` / `itemToChipLabel` / `itemToSelectedLabel` / `renderChips`
|
|
28
|
+
* override can keep the default naming instead of retyping the labels.
|
|
29
|
+
* - The two blank values get their own labels; any other whitespace-only string (a tab, a run of
|
|
30
|
+
* spaces) falls back to a generic one, so two different ones read alike.
|
|
31
|
+
* - Identity is unaffected — `toLabel` keys items off the raw string.
|
|
32
|
+
*/
|
|
33
|
+
const namedLabel = (text) => text.trim() === "" ? BLANK_LABELS[text] ?? "[whitespace]" : text;
|
|
34
|
+
/**
|
|
35
|
+
* `values` with both blank values offered ahead of them, and duplicates collapsed.
|
|
36
|
+
*
|
|
37
|
+
* - The dedupe matters where the list is the distinct values of a Storage column: it can already
|
|
38
|
+
* hold a blank, and two `Object.is`-equal rows both report `aria-selected` and toggle each other.
|
|
39
|
+
* - First occurrence wins, so the order of the values is otherwise preserved.
|
|
40
|
+
* - String items only — the seven Storage filters this serves are all string-valued.
|
|
41
|
+
*/
|
|
42
|
+
const withBlankValues = (values) => [.../* @__PURE__ */ new Set([...BLANK_VALUES, ...values])];
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/components/Combobox/ComboboxChip.tsx
|
|
45
|
+
/**
|
|
46
|
+
* Keeps a chip's own keyboard handling off its content's.
|
|
47
|
+
*
|
|
48
|
+
* - Base UI's chip calls `preventDefault` on Enter and Space, which would cancel the activation of
|
|
49
|
+
* a link or button rendered inside the chip.
|
|
50
|
+
* - Capture phase, because React dispatches capture and bubble in one pass over the event path:
|
|
51
|
+
* stopping here means the chip's own bubble handler never runs.
|
|
52
|
+
* - Guarded on the target, so keys the chip itself receives while focused — Backspace, Delete, the
|
|
53
|
+
* arrows — still reach that handler.
|
|
54
|
+
*/
|
|
55
|
+
const stopNestedKeyDown = (event) => {
|
|
56
|
+
if (event.target !== event.currentTarget) event.stopPropagation();
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* One chip in a `MultiCombobox`'s chip area — Badge chrome plus the remove button.
|
|
60
|
+
*
|
|
61
|
+
* - Position is the identity: Base UI removes the chip at an index, so render one per entry of
|
|
62
|
+
* `value`, in that order. There is no value prop to pass.
|
|
63
|
+
* - `children` is the chip's content, so it can hold a link or an action of its own.
|
|
64
|
+
* - Renders inside a `MultiCombobox`'s `renderChips` slot, or `Combobox.Chips`.
|
|
65
|
+
* - A chip the user cannot remove is a plain `Badge`, not a chip — a chip would take a position
|
|
66
|
+
* that a removal counts against.
|
|
67
|
+
*/
|
|
68
|
+
const ComboboxChip = ({ children, color, variant }) => {
|
|
69
|
+
const renderArgo = useArgoDesign();
|
|
70
|
+
return /* @__PURE__ */ jsxs(Combobox.Chip, {
|
|
71
|
+
render: /* @__PURE__ */ jsx(Badge.Root, {
|
|
72
|
+
color,
|
|
73
|
+
variant: variant ?? (renderArgo ? "pill" : "default"),
|
|
74
|
+
className: "tw:max-w-40"
|
|
75
|
+
}),
|
|
76
|
+
onKeyDownCapture: stopNestedKeyDown,
|
|
77
|
+
children: [/* @__PURE__ */ jsx(Badge.Label, { children }), /* @__PURE__ */ jsx(Badge.Addon, {
|
|
78
|
+
variant: "inline",
|
|
79
|
+
align: "inline-end",
|
|
80
|
+
children: /* @__PURE__ */ jsx(Combobox.ChipRemove, { render: /* @__PURE__ */ jsx(Badge.Button, {
|
|
81
|
+
"aria-label": "Remove value",
|
|
82
|
+
children: /* @__PURE__ */ jsx(X, { className: "tw:size-3" })
|
|
83
|
+
}) })
|
|
84
|
+
})]
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
10
88
|
//#region src/components/Combobox/utils.ts
|
|
11
89
|
const isPlainObject = (item) => item !== null && typeof item === "object";
|
|
12
90
|
const isNumber = (item) => typeof item === "number";
|
|
13
91
|
const isString = (item) => typeof item === "string";
|
|
14
92
|
const isLabeledItem = (item) => isPlainObject(item) && Reflect.has(item, "label") && Reflect.has(item, "value");
|
|
15
93
|
const isInternalItem = (item) => isLabeledItem(item) && "__marker" in item;
|
|
94
|
+
const isSelectAllItem = (item) => isInternalItem(item) && item.__marker === "selectAll";
|
|
16
95
|
const isCreatableItem = (item) => {
|
|
17
96
|
return isInternalItem(item) && item.__marker === "creatable";
|
|
18
97
|
};
|
|
@@ -22,12 +101,29 @@ const isGroupItems = (items) => {
|
|
|
22
101
|
if (items.length === 0) return false;
|
|
23
102
|
return isGroupItem(items[0]);
|
|
24
103
|
};
|
|
104
|
+
/**
|
|
105
|
+
* Node a list row and a chip show for an item.
|
|
106
|
+
*
|
|
107
|
+
* - Names a blank string, because a row or a chip rendering as nothing is indistinguishable from
|
|
108
|
+
* an empty control. {@link toLabel} deliberately does not, so the input keeps the raw value.
|
|
109
|
+
* - A non-string `label` (a JSX node) passes through untouched.
|
|
110
|
+
*/
|
|
25
111
|
const defaultItemToLabel = (item) => {
|
|
26
|
-
if (isNumber(item)
|
|
112
|
+
if (isNumber(item)) return item;
|
|
113
|
+
if (isString(item)) return namedLabel(item);
|
|
27
114
|
if (!isLabeledItem(item)) throw new Error("Combobox: Item does not have a `label` field. Pass `itemToLabel` for custom item shapes.");
|
|
28
|
-
return item.label;
|
|
115
|
+
return isString(item.label) ? namedLabel(item.label) : item.label;
|
|
29
116
|
};
|
|
30
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Display string for an item — mirrors base-ui's own default logic.
|
|
119
|
+
*
|
|
120
|
+
* - Also the default search text and the closed `Combobox`'s input value, so it is the item's
|
|
121
|
+
* string identity rather than its decoration.
|
|
122
|
+
* - Blank strings are deliberately **not** named here: a `''` reaching this is as likely to be an
|
|
123
|
+
* owner's "nothing selected" as a real value.
|
|
124
|
+
* - Naming one would paint text into an empty input and suppress the placeholder.
|
|
125
|
+
* - Naming lives in {@link defaultItemToLabel}, which only rows and chips read.
|
|
126
|
+
*/
|
|
31
127
|
const toLabel = (item) => {
|
|
32
128
|
if (isString(item)) return item;
|
|
33
129
|
if (isLabeledItem(item)) {
|
|
@@ -48,11 +144,28 @@ const toValueKey = (item) => {
|
|
|
48
144
|
const defaultItemToIcon = (item) => isLabeledItem(item) ? item.icon : void 0;
|
|
49
145
|
const defaultItemToDisabled = (item) => isLabeledItem(item) ? item.disabled ?? false : false;
|
|
50
146
|
const defaultGroupToLabel = () => void 0;
|
|
147
|
+
const selectAllItem = {
|
|
148
|
+
__marker: "selectAll",
|
|
149
|
+
value: "__select_all__",
|
|
150
|
+
label: "Select All"
|
|
151
|
+
};
|
|
51
152
|
const createCreatableItem = (query) => ({
|
|
52
153
|
__marker: "creatable",
|
|
53
154
|
value: query,
|
|
54
155
|
label: query
|
|
55
156
|
});
|
|
157
|
+
/**
|
|
158
|
+
* Item-equality predicate for a Base UI combobox root.
|
|
159
|
+
*
|
|
160
|
+
* - An injected sentinel row equals only a sentinel of the same marker with the same value key —
|
|
161
|
+
* the create row is rebuilt on every keystroke, so reference equality would never match it.
|
|
162
|
+
* - A sentinel never equals a real item, however the two keys read: that is what the marker is for.
|
|
163
|
+
* - Real items fall through to the caller's predicate, then to Base UI's own `Object.is`.
|
|
164
|
+
*/
|
|
165
|
+
const toItemEquality = (isItemEqualToValue) => (a, b) => {
|
|
166
|
+
if (isInternalItem(a) || isInternalItem(b)) return isInternalItem(a) && isInternalItem(b) && a.__marker === b.__marker && toValueKey(a) === toValueKey(b);
|
|
167
|
+
return isItemEqualToValue?.(a, b) ?? Object.is(a, b);
|
|
168
|
+
};
|
|
56
169
|
const defaultToCreateValue = (query, value, queryToCreateValue) => {
|
|
57
170
|
if (queryToCreateValue) return queryToCreateValue(query);
|
|
58
171
|
if (value === void 0) return query;
|
|
@@ -66,7 +179,9 @@ const defaultToCreateValue = (query, value, queryToCreateValue) => {
|
|
|
66
179
|
const defaultQueryToCreateLabel = (query) => `Create "${query}"`;
|
|
67
180
|
//#endregion
|
|
68
181
|
//#region src/components/Combobox/ComboboxChips.tsx
|
|
69
|
-
const ComboboxChips = ({ maxVisible = 3, itemToChipLabel = defaultItemToLabel, itemToChipColor, pinnedChipLabels, variant = "pill" }) => /* @__PURE__ */ jsx(Combobox.Value, { children: (
|
|
182
|
+
const ComboboxChips = ({ maxVisible = 3, itemToChipLabel = defaultItemToLabel, itemToChipColor, pinnedChipLabels, variant = "pill", children }) => /* @__PURE__ */ jsx(Combobox.Value, { children: (rawValues) => {
|
|
183
|
+
const values = rawValues.filter((value) => !isInternalItem(value));
|
|
184
|
+
if (children) return children(values);
|
|
70
185
|
const visible = values.slice(0, maxVisible);
|
|
71
186
|
const overflow = values.slice(maxVisible);
|
|
72
187
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
@@ -75,20 +190,10 @@ const ComboboxChips = ({ maxVisible = 3, itemToChipLabel = defaultItemToLabel, i
|
|
|
75
190
|
variant,
|
|
76
191
|
children: label
|
|
77
192
|
}, index)),
|
|
78
|
-
visible.map((value, index) => /* @__PURE__ */
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
variant
|
|
83
|
-
}),
|
|
84
|
-
children: [/* @__PURE__ */ jsx(Badge.Label, { children: itemToChipLabel(value) }), /* @__PURE__ */ jsx(Badge.Addon, {
|
|
85
|
-
variant: "inline",
|
|
86
|
-
align: "inline-end",
|
|
87
|
-
children: /* @__PURE__ */ jsx(Combobox.ChipRemove, { render: /* @__PURE__ */ jsx(Badge.Button, {
|
|
88
|
-
"aria-label": "Remove value",
|
|
89
|
-
children: /* @__PURE__ */ jsx(X, { className: "tw:size-3" })
|
|
90
|
-
}) })
|
|
91
|
-
})]
|
|
193
|
+
visible.map((value, index) => /* @__PURE__ */ jsx(ComboboxChip, {
|
|
194
|
+
color: itemToChipColor?.(value),
|
|
195
|
+
variant,
|
|
196
|
+
children: itemToChipLabel(value)
|
|
92
197
|
}, index)),
|
|
93
198
|
overflow.length > 0 && /* @__PURE__ */ jsx(Badge.Root, {
|
|
94
199
|
variant: "pill",
|
|
@@ -102,20 +207,23 @@ const ComboboxContent = ({ ref, align = "start", side = "bottom", sideOffset = 4
|
|
|
102
207
|
const effectiveVariant = useArgoDesign() ? "argo" : "default";
|
|
103
208
|
return /* @__PURE__ */ jsx(Combobox.Portal, {
|
|
104
209
|
container,
|
|
105
|
-
children: /* @__PURE__ */ jsx(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
210
|
+
children: /* @__PURE__ */ jsx(DismissableLayer, {
|
|
211
|
+
asChild: true,
|
|
212
|
+
children: /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
213
|
+
align,
|
|
214
|
+
side,
|
|
215
|
+
sideOffset,
|
|
216
|
+
alignOffset,
|
|
217
|
+
anchor,
|
|
218
|
+
className: "tw:isolate tw:z-[1055] tailwindcss-preflight",
|
|
219
|
+
children: /* @__PURE__ */ jsx(Combobox.Popup, {
|
|
220
|
+
ref,
|
|
221
|
+
"data-chips": !!anchor,
|
|
222
|
+
"data-variant": effectiveVariant,
|
|
223
|
+
className: cn("tw:!pointer-events-auto", "tw:group/combobox-content", "tw:relative", "tw:flex tw:flex-col tw:overflow-hidden", "tw:max-h-[var(--available-height)]", "tw:w-[var(--anchor-width)]", "tw:max-w-[var(--available-width)]", "tw:origin-[var(--transform-origin)]", "tw:data-[chips=true]:min-w-[var(--anchor-width)]", "tw:rounded-lg", "tw:data-[variant=default]:rounded", "tw:bg-popover", "tw:text-popover-foreground", "tw:shadow-md", "tw:ring-1", "tw:ring-foreground/10", "tw:duration-100", "data-open:animate-in", "data-open:fade-in-0", "data-open:zoom-in-95", "data-closed:animate-out", "data-closed:fade-out-0", "data-closed:zoom-out-95", "tw:data-[side=bottom]:slide-in-from-top-2", "tw:data-[side=top]:slide-in-from-bottom-2", "tw:data-[side=left]:slide-in-from-right-2", "tw:data-[side=right]:slide-in-from-left-2", "tw:data-[side=inline-start]:slide-in-from-right-2", "tw:data-[side=inline-end]:slide-in-from-left-2", className),
|
|
224
|
+
...popupProps,
|
|
225
|
+
children
|
|
226
|
+
})
|
|
119
227
|
})
|
|
120
228
|
})
|
|
121
229
|
});
|
|
@@ -219,27 +327,50 @@ const ComboboxCollection = ({ ...props }) => /* @__PURE__ */ jsx(Combobox.Collec
|
|
|
219
327
|
...props
|
|
220
328
|
});
|
|
221
329
|
//#endregion
|
|
330
|
+
//#region src/components/Combobox/query.ts
|
|
331
|
+
/**
|
|
332
|
+
* The text a combobox's input takes, with `inputValueSanitizer` applied to what the user typed.
|
|
333
|
+
*
|
|
334
|
+
* - `input-change` is the only reason carrying typed text. Every other one carries text the
|
|
335
|
+
* control itself put there — an item's label, or the empty string a clear leaves — which a
|
|
336
|
+
* sanitizer aimed at typed characters would mangle.
|
|
337
|
+
* - A sanitizer sees the whole text on every keystroke, so it has to rewrite rather than drop:
|
|
338
|
+
* one calling `trim()` swallows the space before the next word.
|
|
339
|
+
*/
|
|
340
|
+
const sanitizeTypedInput = (next, reason, sanitizer) => reason === "input-change" ? sanitizer?.(next) ?? next : next;
|
|
341
|
+
/**
|
|
342
|
+
* Reports the query the list filters by to the control's owner.
|
|
343
|
+
*
|
|
344
|
+
* - Fires from an effect rather than the input handler because `query` is derived: in `Combobox`
|
|
345
|
+
* a selection showing its own label empties it without a keystroke, so the handler never sees
|
|
346
|
+
* every value it takes.
|
|
347
|
+
* - The mount render reports too, so an owner fetching on the query needs no first fetch of its
|
|
348
|
+
* own.
|
|
349
|
+
*/
|
|
350
|
+
const useQueryReporter = (query, onQueryChange) => {
|
|
351
|
+
const report = useStableCallback(onQueryChange ?? noop);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
report(query);
|
|
354
|
+
}, [query, report]);
|
|
355
|
+
};
|
|
356
|
+
//#endregion
|
|
222
357
|
//#region src/components/Combobox/Combobox.tsx
|
|
223
358
|
const VIRTUALIZE_THRESHOLD = 30;
|
|
224
359
|
const ESTIMATED_ROW_HEIGHT = 40;
|
|
225
360
|
const ESTIMATED_LIST_HEIGHT = 320;
|
|
226
|
-
const Combobox$1 = ({ items: _itemsProp, value, defaultValue, onValueChange, itemToStringLabel: itemToStringLabelProp, itemToStringValue, disabled, filter, inputRef, onOpenChangeComplete, isItemEqualToValue: isItemEqualToValueProp, variant, sizing, focusVariant, state, placeholder, allowClear, id, className, empty = "No items", indicator, prefix, virtualized = false, allowCreate, queryToCreateValue, queryToCreateLabel = defaultQueryToCreateLabel, canCreate, itemToDisabled = defaultItemToDisabled, itemToLabel = defaultItemToLabel, itemToIcon = defaultItemToIcon, groupToLabel = defaultGroupToLabel }) => {
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
setInputValue((prev) => prev === label ? prev : label);
|
|
238
|
-
}, [value, selections]);
|
|
239
|
-
const selectedLabel = value == null ? void 0 : itemToStringLabelProp?.(value) ?? toLabel(value);
|
|
240
|
-
const isShowingSelection = selectedLabel !== void 0 && inputValue === selectedLabel;
|
|
241
|
-
const query = (isShowingSelection ? "" : inputValue).trim();
|
|
361
|
+
const Combobox$1 = ({ items: _itemsProp, value, defaultValue, onValueChange, itemToStringLabel: itemToStringLabelProp, itemToStringValue, disabled, filter, inputRef, onOpenChangeComplete, isItemEqualToValue: isItemEqualToValueProp, variant, sizing, focusVariant, state, placeholder, allowClear = true, id, className, empty = "No items", indicator, prefix, virtualized = false, inputValueSanitizer, onQueryChange, allowCreate, queryToCreateValue, queryToCreateLabel = defaultQueryToCreateLabel, canCreate, itemToDisabled = defaultItemToDisabled, itemToLabel = defaultItemToLabel, itemToIcon = defaultItemToIcon, itemToSelectedLabel, groupToLabel = defaultGroupToLabel }) => {
|
|
362
|
+
/** An item's string identity: the input's text, the default search subject, and the accessible value. */
|
|
363
|
+
const labelOf = (item) => itemToStringLabelProp?.(item) ?? toLabel(item);
|
|
364
|
+
const [typedText, setTypedText] = useState(null);
|
|
365
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? null);
|
|
366
|
+
const selectedItem = value !== void 0 ? value : uncontrolledValue;
|
|
367
|
+
const selectedLabel = selectedItem == null ? null : labelOf(selectedItem);
|
|
368
|
+
const isShowingSelection = selectedLabel !== null && (typedText === null || typedText === selectedLabel);
|
|
369
|
+
const inputValue = typedText ?? selectedLabel ?? "";
|
|
370
|
+
const selectedNode = itemToSelectedLabel && selectedItem != null && isShowingSelection ? itemToSelectedLabel(selectedItem) : null;
|
|
371
|
+
const query = (isShowingSelection ? "" : typedText ?? "").trim();
|
|
242
372
|
const queryLower = query.toLowerCase();
|
|
373
|
+
useQueryReporter(query, onQueryChange);
|
|
243
374
|
const itemsProp = _itemsProp ?? [];
|
|
244
375
|
const sampleItem = itemsProp.flatMap((item) => isGroupItem(item) ? item.items : [item])[0];
|
|
245
376
|
const [createdItems, setCreatedItems] = useState([]);
|
|
@@ -251,49 +382,35 @@ const Combobox$1 = ({ items: _itemsProp, value, defaultValue, onValueChange, ite
|
|
|
251
382
|
const items = (() => {
|
|
252
383
|
if (!allowCreate || disabled) return allItems;
|
|
253
384
|
if (!query) return allItems;
|
|
254
|
-
if (allItems.flatMap((item) => isGroupItem(item) ? item.items : [item]).some((item) => (
|
|
385
|
+
if (allItems.flatMap((item) => isGroupItem(item) ? item.items : [item]).some((item) => labelOf(item).toLowerCase() === queryLower)) return allItems;
|
|
255
386
|
if (canCreate && !canCreate(query)) return allItems;
|
|
256
387
|
const creatable = isGroupItems(itemsProp) ? [{ items: [createCreatableItem(query)] }] : [createCreatableItem(query)];
|
|
257
388
|
return [...allItems, ...creatable];
|
|
258
389
|
})();
|
|
259
|
-
const itemToStringLabel = (value) =>
|
|
260
|
-
if (isCreatableItem(value)) return value.value;
|
|
261
|
-
return itemToStringLabelProp?.(value) ?? toLabel(value);
|
|
262
|
-
};
|
|
390
|
+
const itemToStringLabel = (value) => isCreatableItem(value) ? value.value : labelOf(value);
|
|
263
391
|
const wrappedFilter = isShowingSelection ? () => true : !allowCreate || !filter ? filter : (item, ...args) => isCreatableItem(item) || filter(item, ...args);
|
|
264
392
|
const wrappedItemToStringValue = !allowCreate || !itemToStringValue ? itemToStringValue : (item) => isCreatableItem(item) ? item.value : itemToStringValue(item);
|
|
265
|
-
const isItemEqualToValue = (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const handleInputValueChange = (value, { reason }) => {
|
|
270
|
-
if (["input-clear", "none"].includes(reason) && ["item-press", "creatable-item-press"].includes(closeReasonRef.current)) {
|
|
271
|
-
closeReasonRef.current = "none";
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
if (reason === "none" && closeReasonRef.current == "outside-click") {
|
|
275
|
-
closeReasonRef.current = "none";
|
|
276
|
-
setInputValue(value);
|
|
393
|
+
const isItemEqualToValue = useMemo(() => toItemEquality(isItemEqualToValueProp), [isItemEqualToValueProp]);
|
|
394
|
+
const handleInputValueChange = (next, { reason }) => {
|
|
395
|
+
if (reason !== "input-change") {
|
|
396
|
+
setTypedText(null);
|
|
277
397
|
return;
|
|
278
398
|
}
|
|
279
|
-
|
|
399
|
+
const typed = inputValueSanitizer?.(next) ?? next;
|
|
400
|
+
setTypedText(typed === "" || typed.trim() !== "" ? typed : null);
|
|
280
401
|
};
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
if (open) return;
|
|
284
|
-
if (closeReasonRef.current !== "none") return;
|
|
285
|
-
closeReasonRef.current = "outside-click";
|
|
402
|
+
const handleOpenChange = (open) => {
|
|
403
|
+
if (!open) setTypedText(null);
|
|
286
404
|
};
|
|
287
405
|
const handleOnValueChange = (next, event) => {
|
|
288
|
-
setSelections((count) => count + 1);
|
|
289
406
|
if (isCreatableItem(next)) {
|
|
290
407
|
const newValue = defaultToCreateValue(next.value, sampleItem, queryToCreateValue);
|
|
291
|
-
closeReasonRef.current = "creatable-item-press";
|
|
292
408
|
setCreatedItems((prev) => [...prev, newValue]);
|
|
409
|
+
setUncontrolledValue(newValue);
|
|
293
410
|
onValueChange?.(newValue, event);
|
|
294
411
|
return;
|
|
295
412
|
}
|
|
296
|
-
|
|
413
|
+
setUncontrolledValue(next);
|
|
297
414
|
onValueChange?.(next, event);
|
|
298
415
|
};
|
|
299
416
|
const [listElement, setListElement] = useState(null);
|
|
@@ -339,6 +456,12 @@ const Combobox$1 = ({ items: _itemsProp, value, defaultValue, onValueChange, ite
|
|
|
339
456
|
width: "100%",
|
|
340
457
|
transform: `translateY(${offset}px)`
|
|
341
458
|
});
|
|
459
|
+
const comboboxInput = /* @__PURE__ */ jsx(ComboboxInput, {
|
|
460
|
+
id,
|
|
461
|
+
placeholder,
|
|
462
|
+
disabled,
|
|
463
|
+
className: cn(selectedNode != null && "tw:text-transparent tw:caret-foreground tw:selection:bg-transparent")
|
|
464
|
+
});
|
|
342
465
|
return /* @__PURE__ */ jsxs(Combobox.Root, {
|
|
343
466
|
items: shouldVirtualize ? virtualItems : items,
|
|
344
467
|
autoHighlight: true,
|
|
@@ -349,7 +472,8 @@ const Combobox$1 = ({ items: _itemsProp, value, defaultValue, onValueChange, ite
|
|
|
349
472
|
itemToStringLabel,
|
|
350
473
|
isItemEqualToValue,
|
|
351
474
|
itemToStringValue: wrappedItemToStringValue,
|
|
352
|
-
|
|
475
|
+
onOpenChange: handleOpenChange,
|
|
476
|
+
onOpenChangeComplete,
|
|
353
477
|
disabled,
|
|
354
478
|
inputRef,
|
|
355
479
|
inputValue,
|
|
@@ -364,31 +488,30 @@ const Combobox$1 = ({ items: _itemsProp, value, defaultValue, onValueChange, ite
|
|
|
364
488
|
state,
|
|
365
489
|
focusVariant,
|
|
366
490
|
className,
|
|
367
|
-
children: [
|
|
368
|
-
|
|
491
|
+
children: [itemToSelectedLabel != null ? /* @__PURE__ */ jsxs("div", {
|
|
492
|
+
className: "tw:relative tw:flex tw:min-w-0 tw:flex-1",
|
|
493
|
+
children: [(prefix != null || selectedNode != null) && /* @__PURE__ */ jsxs(InputGroup.Addon, {
|
|
369
494
|
align: "inline-start",
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
})
|
|
391
|
-
]
|
|
495
|
+
className: cn(selectedNode != null && "tw:pointer-events-none tw:absolute tw:inset-y-0 tw:left-0 tw:max-w-full tw:overflow-hidden"),
|
|
496
|
+
children: [prefix, selectedNode]
|
|
497
|
+
}), comboboxInput]
|
|
498
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [prefix != null && /* @__PURE__ */ jsx(InputGroup.Addon, {
|
|
499
|
+
align: "inline-start",
|
|
500
|
+
children: prefix
|
|
501
|
+
}), comboboxInput] }), /* @__PURE__ */ jsxs(InputGroup.Addon, {
|
|
502
|
+
align: "inline-end",
|
|
503
|
+
children: [allowClear && /* @__PURE__ */ jsx(InputGroup.Button, {
|
|
504
|
+
asChild: true,
|
|
505
|
+
"aria-label": "Clear",
|
|
506
|
+
disabled,
|
|
507
|
+
children: /* @__PURE__ */ jsx(Combobox.Clear, { children: /* @__PURE__ */ jsx(X, { className: "tw:size-4" }) })
|
|
508
|
+
}), /* @__PURE__ */ jsx(InputGroup.Button, {
|
|
509
|
+
disabled,
|
|
510
|
+
asChild: true,
|
|
511
|
+
"aria-label": "Open combobox",
|
|
512
|
+
children: /* @__PURE__ */ jsx(Combobox.Trigger, { children: /* @__PURE__ */ jsx(ChevronDown, { className: "tw:size-4" }) })
|
|
513
|
+
})]
|
|
514
|
+
})]
|
|
392
515
|
}), /* @__PURE__ */ jsxs(ComboboxContent, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: empty }), shouldVirtualize ? /* @__PURE__ */ jsx(ComboboxList, {
|
|
393
516
|
ref: setListElement,
|
|
394
517
|
children: /* @__PURE__ */ jsx("div", {
|
|
@@ -444,7 +567,53 @@ Combobox$1.Group = ComboboxGroup;
|
|
|
444
567
|
Combobox$1.GroupLabel = ComboboxGroupLabel;
|
|
445
568
|
Combobox$1.GroupList = ComboboxCollection;
|
|
446
569
|
Combobox$1.Chips = ComboboxChips;
|
|
570
|
+
Combobox$1.Chip = ComboboxChip;
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/components/Combobox/matchByWords.ts
|
|
573
|
+
const toWords = (query) => query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
574
|
+
const isFilled = (text) => text !== null && text !== void 0 && text !== "";
|
|
575
|
+
const isTextList = (texts) => Array.isArray(texts);
|
|
576
|
+
/**
|
|
577
|
+
* Whether every word of `query` occurs in at least one of `texts`, ignoring case.
|
|
578
|
+
*
|
|
579
|
+
* - Words may match different texts and in any order: `prod eu` matches `['eu-central', 'production']`.
|
|
580
|
+
* - A word matches by containment, not as a whole word: `prod` matches `production`.
|
|
581
|
+
* - An empty or whitespace-only query matches every item.
|
|
582
|
+
* - Empty and absent texts are skipped, so a missing field never matches.
|
|
583
|
+
* - Comparison is a plain lowercased `includes`, not Base UI's `Intl.Collator` `contains`, so
|
|
584
|
+
* `Muller` does not find `Müller`. The wrapper this replaces matched the same way.
|
|
585
|
+
* - `apps/kbc-ui` has its own `matchByWords` of the same name over 68 mostly non-combobox call
|
|
586
|
+
* sites. It splits on a single space and keeps empty texts, so the two are not interchangeable
|
|
587
|
+
* until that one re-exports this.
|
|
588
|
+
*
|
|
589
|
+
* Reach for it as a `Combobox` / `MultiCombobox` `filter` when the searchable text is **not** the
|
|
590
|
+
* label — a user's email behind their name, a table id behind its display name:
|
|
591
|
+
*
|
|
592
|
+
* ```tsx
|
|
593
|
+
* <Combobox filter={(user, query) => matchByWords([user.name, user.email], query)} … />
|
|
594
|
+
* ```
|
|
595
|
+
*
|
|
596
|
+
* When only the label is searchable, pass {@link filterByWords} instead of closing over this.
|
|
597
|
+
*/
|
|
598
|
+
const matchByWords = (texts, query) => {
|
|
599
|
+
const words = toWords(query);
|
|
600
|
+
if (words.length === 0) return true;
|
|
601
|
+
const haystacks = (isTextList(texts) ? texts : [texts]).filter(isFilled).map((text) => String(text).toLowerCase());
|
|
602
|
+
return words.every((word) => haystacks.some((haystack) => haystack.includes(word)));
|
|
603
|
+
};
|
|
604
|
+
/**
|
|
605
|
+
* `filter` that matches the item's displayed label word by word.
|
|
606
|
+
*
|
|
607
|
+
* - Drop-in for the built-in match, which takes the query as one substring: `filter={filterByWords}`.
|
|
608
|
+
* - Generic, so passing it leaves the combobox's own item type inference untouched.
|
|
609
|
+
* - The label is what the combobox itself displays — `itemToStringLabel` where the call site passes
|
|
610
|
+
* one, otherwise the item's own `label`, `value`, or string form.
|
|
611
|
+
* - **Only the label is searched.** Nothing walks the rest of the item, so a field the label omits
|
|
612
|
+
* stays unsearchable by design — a filter that quietly matched an id the user cannot see is
|
|
613
|
+
* worse than one that does not match. Close over {@link matchByWords} to name those fields.
|
|
614
|
+
*/
|
|
615
|
+
const filterByWords = (item, query, itemToString) => matchByWords(itemToString?.(item) ?? toLabel(item), query);
|
|
447
616
|
//#endregion
|
|
448
|
-
export { Combobox$1 as Combobox, createCreatableItem, defaultGroupToLabel, defaultItemToDisabled, defaultItemToIcon, defaultItemToLabel, defaultQueryToCreateLabel, defaultToCreateValue, isCreatableItem, isGroupItem, isGroupItems,
|
|
617
|
+
export { Combobox$1 as Combobox, createCreatableItem, defaultGroupToLabel, defaultItemToDisabled, defaultItemToIcon, defaultItemToLabel, defaultQueryToCreateLabel, defaultToCreateValue, filterByWords, isCreatableItem, isGroupItem, isGroupItems, isSelectAllItem, matchByWords, namedLabel, sanitizeTypedInput, selectAllItem, toItemEquality, toLabel, useQueryReporter, withBlankValues };
|
|
449
618
|
|
|
450
|
-
//# sourceMappingURL=Combobox-
|
|
619
|
+
//# sourceMappingURL=Combobox-BfIfaVMA.js.map
|