@allxsmith/bestax-bulma 5.54.0 → 5.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/Command.js +22 -22
  4. package/dist/esm/components/Command.js.map +1 -1
  5. package/dist/esm/components/DataTable.js +215 -0
  6. package/dist/esm/components/DataTable.js.map +1 -0
  7. package/dist/esm/components/useDataTable.js +482 -0
  8. package/dist/esm/components/useDataTable.js.map +1 -0
  9. package/dist/esm/elements/ColorSwatch.js +49 -0
  10. package/dist/esm/elements/ColorSwatch.js.map +1 -0
  11. package/dist/esm/elements/RingProgress.js +3 -3
  12. package/dist/esm/elements/Td.js +4 -2
  13. package/dist/esm/elements/Td.js.map +1 -1
  14. package/dist/esm/elements/Th.js +13 -5
  15. package/dist/esm/elements/Th.js.map +1 -1
  16. package/dist/esm/elements/Tr.js.map +1 -1
  17. package/dist/esm/form/ColorPicker.js +540 -0
  18. package/dist/esm/form/ColorPicker.js.map +1 -0
  19. package/dist/esm/form/Combobox.js +18 -18
  20. package/dist/esm/form/Combobox.js.map +1 -1
  21. package/dist/esm/form/Dropzone.js +524 -0
  22. package/dist/esm/form/Dropzone.js.map +1 -0
  23. package/dist/esm/form/File.js.map +1 -1
  24. package/dist/esm/form/_colorInternals/colorMath.js +292 -0
  25. package/dist/esm/form/_colorInternals/colorMath.js.map +1 -0
  26. package/dist/esm/form/_colorInternals/pickerState.js +160 -0
  27. package/dist/esm/form/_colorInternals/pickerState.js.map +1 -0
  28. package/dist/esm/helpers/statusRegion.js +3 -3
  29. package/dist/esm/helpers/statusRegion.js.map +1 -1
  30. package/dist/esm/helpers/useFocusTrap.js +2 -2
  31. package/dist/extras.css +1 -1
  32. package/dist/extras.css.map +1 -1
  33. package/dist/index.cjs +2986 -796
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.esm.js +5 -0
  36. package/dist/index.esm.js.map +1 -1
  37. package/dist/types/components/DataTable.d.ts +120 -0
  38. package/dist/types/components/useDataTable.d.ts +445 -0
  39. package/dist/types/elements/ColorSwatch.d.ts +54 -0
  40. package/dist/types/elements/Td.d.ts +9 -1
  41. package/dist/types/elements/Th.d.ts +14 -2
  42. package/dist/types/elements/Tr.d.ts +10 -1
  43. package/dist/types/form/ColorPicker.d.ts +266 -0
  44. package/dist/types/form/Dropzone.d.ts +138 -0
  45. package/dist/types/form/File.d.ts +2 -0
  46. package/dist/types/form/_colorInternals/colorMath.d.ts +63 -0
  47. package/dist/types/form/_colorInternals/pickerState.d.ts +62 -0
  48. package/dist/types/index.d.ts +4 -0
  49. package/dist/types-cjs/components/DataTable.d.ts +120 -0
  50. package/dist/types-cjs/components/useDataTable.d.ts +445 -0
  51. package/dist/types-cjs/elements/ColorSwatch.d.ts +54 -0
  52. package/dist/types-cjs/elements/Td.d.ts +9 -1
  53. package/dist/types-cjs/elements/Th.d.ts +14 -2
  54. package/dist/types-cjs/elements/Tr.d.ts +10 -1
  55. package/dist/types-cjs/form/ColorPicker.d.ts +266 -0
  56. package/dist/types-cjs/form/Dropzone.d.ts +138 -0
  57. package/dist/types-cjs/form/File.d.ts +2 -0
  58. package/dist/types-cjs/form/_colorInternals/colorMath.d.ts +63 -0
  59. package/dist/types-cjs/form/_colorInternals/pickerState.d.ts +62 -0
  60. package/dist/types-cjs/index.d.ts +4 -0
  61. package/dist/versions/bestax-no-dark-mode.css +1 -1
  62. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  63. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  64. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  65. package/dist/versions/bestax-no-helpers.css +1 -1
  66. package/dist/versions/bestax-no-helpers.css.map +1 -1
  67. package/dist/versions/bestax-prefixed.css +1 -1
  68. package/dist/versions/bestax-prefixed.css.map +1 -1
  69. package/package.json +1 -1
  70. package/src/scss/components/_datatable.scss +152 -0
  71. package/src/scss/components/_index.scss +1 -0
  72. package/src/scss/elements/_colorswatch.scss +100 -0
  73. package/src/scss/elements/_index.scss +1 -0
  74. package/src/scss/form/_checkbox.scss +53 -0
  75. package/src/scss/form/_colorpicker.scss +354 -0
  76. package/src/scss/form/_dropzone.scss +233 -0
  77. package/src/scss/form/_file.scss +29 -1
  78. package/src/scss/form/_index.scss +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"File.js","sources":["../../../src/form/File.tsx"],"sourcesContent":["import React, { forwardRef, useEffect, useState } from 'react';\nimport {\n classNames,\n usePrefixedClassNames,\n prefixedClassNames,\n} from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { useConfig } from '../helpers/Config';\nimport { useNumberFormat } from '../helpers/localeFormat';\nimport {\n useInsideField,\n useInsideControl,\n rendersOwnField,\n} from './FormContext';\nimport { Field } from './Field';\nimport { FormFieldProps } from './fieldProps';\nimport { useAutoLabelId } from './useAutoLabelId';\n\n/**\n * Props for the File component.\n * Inside an outer `Field`, or a `Control` with no `Field` around it, it\n * renders no `Field` of its own. The exception is `label`, `message`,\n * `horizontal` or `fieldClassName` in that bare `Control`: it keeps a `Field`\n * for them, nested in the `.control`, and warns in development. Wrap the\n * `Control` in a `Field` instead, and set the `label`, `horizontal` and class\n * name on that `Field`.\n *\n * @helperProps Helper props go on the `.file` wrapper `<div>`, with\n * `className`. The other attributes go on the `<input>`.\n */\nexport interface FileProps\n extends\n Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'size' | 'color' | 'type'\n >,\n Omit<BulmaClassesProps, 'color'>,\n FormFieldProps {\n /** Field label. Automatically associated with the file input via `htmlFor` — uses your `id` when provided, otherwise a generated one. The input then has two labels (this one plus the wrapping `file-label`); assistive tech reads both. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here. */\n label?: React.ReactNode;\n /** Props for the label element. An explicit `htmlFor` here overrides the automatic association (no id is generated then). */\n labelProps?: React.LabelHTMLAttributes<HTMLLabelElement> & {\n [key: string]: unknown;\n };\n /** Bulma color modifier for the file input. */\n color?:\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'black'\n | 'dark'\n | 'light'\n | 'white';\n /** Size modifier for the file input. */\n size?: 'small' | 'medium' | 'large';\n /** Boxed file input. */\n isBoxed?: boolean;\n /** Whether the file input expands to full width. */\n isFullwidth?: boolean;\n /** Whether the file input expands to full width. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */\n isFullWidth?: boolean;\n /** Position the CTA on the right (with `hasName`). */\n isRight?: boolean;\n /** Center the file input within its container. */\n isCentered?: boolean;\n /**\n * Show a file name area. Without `fileName` it shows what the user picked: the file's name, or\n * a count when `multiple` lets them pick several (see `pickedFilesLabel`). Before a pick there is\n * no name area, and the root takes Bulma's `is-empty`. A reset of the input's form clears the\n * name, but clearing the input from code fires no change event and leaves it showing, so pass\n * `fileName` to control the text then.\n */\n hasName?: boolean;\n /** Text on the file CTA button (defaults to \"Choose a file…\"). */\n buttonLabel?: React.ReactNode;\n /**\n * Left icon element. It renders inside the `<label>` that names the file input, beside\n * `buttonLabel`, so leave an `Icon` here unnamed (`<Icon name=\"upload\" />`): it is then\n * `aria-hidden`, while an `ariaLabel` would join the input's accessible name.\n */\n iconLeft?: React.ReactNode;\n /**\n * Right icon element. Sits inside the same `<label>` as `iconLeft`, so the same applies:\n * an `Icon` here with an `ariaLabel` joins the input's accessible name.\n */\n iconRight?: React.ReactNode;\n /** Additional CSS classes to apply. */\n className?: string;\n /** Additional CSS classes for the `<input>`. */\n inputClassName?: string;\n /**\n * Text for the file name area (with `hasName`), for a file chosen earlier or your own wording.\n * Setting it takes over from the picked file's name; an empty string shows no name area.\n */\n fileName?: string;\n /**\n * Builds the text `hasName` shows when several files are picked, from their count, for\n * localization. Default: `` `${count} files` ``, with the count written the way the `locale`\n * of a `LocaleProvider` around it writes numbers. A single pick shows its file's name.\n */\n pickedFilesLabel?: (count: number) => string;\n}\n\n/**\n * The `File` component provides a Bulma-styled file input, supporting color, size, boxed/fullwidth/align styles, icons, \"has name\", and filename display.\n *\n * @function\n * @param {FileProps} props - Props for the File component.\n * @returns {JSX.Element} The rendered file upload field.\n * @see {@link https://bulma.io/documentation/form/file/ | Bulma File documentation}\n */\nexport const File = forwardRef<HTMLInputElement, FileProps>(\n (\n {\n // Field props\n label,\n labelSize,\n labelProps,\n horizontal,\n message,\n messageColor,\n fieldClassName,\n color,\n size,\n isBoxed,\n isFullwidth,\n isFullWidth,\n isRight,\n isCentered,\n hasName,\n buttonLabel,\n iconLeft,\n iconRight,\n className,\n inputClassName,\n fileName,\n pickedFilesLabel,\n onChange,\n ...props\n },\n ref\n ) => {\n // What the user last picked, and the form a reset of which clears it.\n const [picked, setPicked] = useState<{\n count: number;\n firstName: string;\n form: HTMLFormElement | null;\n }>();\n const pickedForm = picked?.form;\n useEffect(() => {\n if (!pickedForm) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n // The reset event fires before the form resets, and a listener after this one can still\n // cancel it, so the name is cleared a task later, and only if the reset went ahead.\n const onReset = (event: Event) => {\n timer = setTimeout(() => {\n if (!event.defaultPrevented) setPicked(undefined);\n });\n };\n pickedForm.addEventListener('reset', onReset);\n return () => {\n pickedForm.removeEventListener('reset', onReset);\n clearTimeout(timer);\n };\n }, [pickedForm]);\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const { files, form } = event.target;\n setPicked(\n files?.length\n ? { count: files.length, firstName: files[0].name, form }\n : undefined\n );\n onChange?.(event);\n };\n const formatNumber = useNumberFormat();\n const pickedText =\n picked &&\n (picked.count === 1\n ? picked.firstName\n : (pickedFilesLabel?.(picked.count) ??\n `${formatNumber ? formatNumber(picked.count) : picked.count} files`));\n const shownName = fileName ?? pickedText;\n\n const insideField = useInsideField();\n const insideControl = useInsideControl();\n const ownField = rendersOwnField('File', {\n insideField,\n insideControl,\n label,\n message,\n horizontal,\n fieldClassName,\n });\n const { controlId, fieldLabelProps } = useAutoLabelId({\n label,\n id: props.id,\n labelProps,\n rendersLabel: ownField,\n });\n const { classPrefix } = useConfig();\n // The input takes `controlId`, so `id` stays out of the spread, where an\n // undefined `id` key would wipe the id the label points at.\n const { id: _id, ...inputProps } = props;\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color,\n ...inputProps,\n });\n\n // Mutually exclusive alignment\n let alignmentClass: string | undefined;\n if (isRight && isCentered) {\n // If both are set, prefer isRight and warn in dev\n alignmentClass = prefixedClassNames(classPrefix, 'is-right');\n } else if (isRight) {\n alignmentClass = prefixedClassNames(classPrefix, 'is-right');\n } else if (isCentered) {\n alignmentClass = prefixedClassNames(classPrefix, 'is-centered');\n }\n\n const mainClass = usePrefixedClassNames('file', {\n [`is-${color}`]: !!color,\n [`is-${size}`]: !!size,\n 'is-boxed': isBoxed,\n 'is-fullwidth': isFullwidth ?? isFullWidth,\n 'has-name': hasName,\n 'is-empty': hasName && !shownName,\n });\n const fileClass = classNames(\n mainClass,\n bulmaHelperClasses,\n alignmentClass,\n className\n );\n\n const helpClass = usePrefixedClassNames('help', {\n [`is-${messageColor}`]: !!messageColor,\n });\n const messageEl = message ? <p className={helpClass}>{message}</p> : null;\n\n const fileElement = (\n <div className={fileClass}>\n <label className={usePrefixedClassNames('file-label')}>\n <input\n ref={ref}\n className={classNames(\n usePrefixedClassNames('file-input'),\n inputClassName\n )}\n type=\"file\"\n id={controlId}\n onChange={handleChange}\n {...rest}\n />\n <span className={usePrefixedClassNames('file-cta')}>\n {iconLeft && (\n <span className={prefixedClassNames(classPrefix, 'file-icon')}>\n {iconLeft}\n </span>\n )}\n <span className={usePrefixedClassNames('file-label')}>\n {buttonLabel || 'Choose a file\\u2026'}\n </span>\n {iconRight && (\n <span className={prefixedClassNames(classPrefix, 'file-icon')}>\n {iconRight}\n </span>\n )}\n </span>\n {hasName && shownName && (\n <span className={prefixedClassNames(classPrefix, 'file-name')}>\n {shownName}\n </span>\n )}\n </label>\n </div>\n );\n\n if (ownField) {\n return (\n <Field\n label={label}\n labelSize={labelSize}\n labelProps={fieldLabelProps}\n horizontal={horizontal}\n className={fieldClassName}\n >\n {fileElement}\n {messageEl}\n </Field>\n );\n }\n\n return (\n <>\n {fileElement}\n {messageEl}\n </>\n );\n }\n);\n\nFile.displayName = 'File';\n\nexport default File;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"File.js","sources":["../../../src/form/File.tsx"],"sourcesContent":["import React, { forwardRef, useEffect, useState } from 'react';\nimport {\n classNames,\n usePrefixedClassNames,\n prefixedClassNames,\n} from '../helpers/classNames';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { useConfig } from '../helpers/Config';\nimport { useNumberFormat } from '../helpers/localeFormat';\nimport {\n useInsideField,\n useInsideControl,\n rendersOwnField,\n} from './FormContext';\nimport { Field } from './Field';\nimport { FormFieldProps } from './fieldProps';\nimport { useAutoLabelId } from './useAutoLabelId';\n\n/**\n * Props for the File component.\n * Inside an outer `Field`, or a `Control` with no `Field` around it, it\n * renders no `Field` of its own. The exception is `label`, `message`,\n * `horizontal` or `fieldClassName` in that bare `Control`: it keeps a `Field`\n * for them, nested in the `.control`, and warns in development. Wrap the\n * `Control` in a `Field` instead, and set the `label`, `horizontal` and class\n * name on that `Field`.\n *\n * @helperProps Helper props go on the `.file` wrapper `<div>`, with\n * `className`. The other attributes go on the `<input>`.\n * @extraProp {string} [accept] - File types the picker offers, as the native attribute takes them (`.pdf`, `image/*`, `application/pdf`). Browsers apply it in the picker only: a file dropped on the input lands whatever its type, so check `files` in `onChange` when the type matters.\n * @extraProp {(event: React.ChangeEvent<HTMLInputElement>) => void} [onChange] - Called when the user picks files, or drops them on the input. With the extras stylesheet loaded, a drop does what a pick does: it replaces what was picked and fires `input` and `change`. A drop of several files lands only with `multiple`.\n */\nexport interface FileProps\n extends\n Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'size' | 'color' | 'type'\n >,\n Omit<BulmaClassesProps, 'color'>,\n FormFieldProps {\n /** Field label. Automatically associated with the file input via `htmlFor` — uses your `id` when provided, otherwise a generated one. The input then has two labels (this one plus the wrapping `file-label`); assistive tech reads both. Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here. */\n label?: React.ReactNode;\n /** Props for the label element. An explicit `htmlFor` here overrides the automatic association (no id is generated then). */\n labelProps?: React.LabelHTMLAttributes<HTMLLabelElement> & {\n [key: string]: unknown;\n };\n /** Bulma color modifier for the file input. */\n color?:\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'black'\n | 'dark'\n | 'light'\n | 'white';\n /** Size modifier for the file input. */\n size?: 'small' | 'medium' | 'large';\n /** Boxed file input. */\n isBoxed?: boolean;\n /** Whether the file input expands to full width. */\n isFullwidth?: boolean;\n /** Whether the file input expands to full width. @deprecated Use `isFullwidth` instead — `isFullwidth` wins if both are set. */\n isFullWidth?: boolean;\n /** Position the CTA on the right (with `hasName`). */\n isRight?: boolean;\n /** Center the file input within its container. */\n isCentered?: boolean;\n /**\n * Show a file name area. Without `fileName` it shows what the user picked: the file's name, or\n * a count when `multiple` lets them pick several (see `pickedFilesLabel`). Before a pick there is\n * no name area, and the root takes Bulma's `is-empty`. A reset of the input's form clears the\n * name, but clearing the input from code fires no change event and leaves it showing, so pass\n * `fileName` to control the text then.\n */\n hasName?: boolean;\n /** Text on the file CTA button (defaults to \"Choose a file…\"). */\n buttonLabel?: React.ReactNode;\n /**\n * Left icon element. It renders inside the `<label>` that names the file input, beside\n * `buttonLabel`, so leave an `Icon` here unnamed (`<Icon name=\"upload\" />`): it is then\n * `aria-hidden`, while an `ariaLabel` would join the input's accessible name.\n */\n iconLeft?: React.ReactNode;\n /**\n * Right icon element. Sits inside the same `<label>` as `iconLeft`, so the same applies:\n * an `Icon` here with an `ariaLabel` joins the input's accessible name.\n */\n iconRight?: React.ReactNode;\n /** Additional CSS classes to apply. */\n className?: string;\n /** Additional CSS classes for the `<input>`. */\n inputClassName?: string;\n /**\n * Text for the file name area (with `hasName`), for a file chosen earlier or your own wording.\n * Setting it takes over from the picked file's name; an empty string shows no name area.\n */\n fileName?: string;\n /**\n * Builds the text `hasName` shows when several files are picked, from their count, for\n * localization. Default: `` `${count} files` ``, with the count written the way the `locale`\n * of a `LocaleProvider` around it writes numbers. A single pick shows its file's name.\n */\n pickedFilesLabel?: (count: number) => string;\n}\n\n/**\n * The `File` component provides a Bulma-styled file input, supporting color, size, boxed/fullwidth/align styles, icons, \"has name\", and filename display.\n *\n * @function\n * @param {FileProps} props - Props for the File component.\n * @returns {JSX.Element} The rendered file upload field.\n * @see {@link https://bulma.io/documentation/form/file/ | Bulma File documentation}\n */\nexport const File = forwardRef<HTMLInputElement, FileProps>(\n (\n {\n // Field props\n label,\n labelSize,\n labelProps,\n horizontal,\n message,\n messageColor,\n fieldClassName,\n color,\n size,\n isBoxed,\n isFullwidth,\n isFullWidth,\n isRight,\n isCentered,\n hasName,\n buttonLabel,\n iconLeft,\n iconRight,\n className,\n inputClassName,\n fileName,\n pickedFilesLabel,\n onChange,\n ...props\n },\n ref\n ) => {\n // What the user last picked, and the form a reset of which clears it.\n const [picked, setPicked] = useState<{\n count: number;\n firstName: string;\n form: HTMLFormElement | null;\n }>();\n const pickedForm = picked?.form;\n useEffect(() => {\n if (!pickedForm) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n // The reset event fires before the form resets, and a listener after this one can still\n // cancel it, so the name is cleared a task later, and only if the reset went ahead.\n const onReset = (event: Event) => {\n timer = setTimeout(() => {\n if (!event.defaultPrevented) setPicked(undefined);\n });\n };\n pickedForm.addEventListener('reset', onReset);\n return () => {\n pickedForm.removeEventListener('reset', onReset);\n clearTimeout(timer);\n };\n }, [pickedForm]);\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const { files, form } = event.target;\n setPicked(\n files?.length\n ? { count: files.length, firstName: files[0].name, form }\n : undefined\n );\n onChange?.(event);\n };\n const formatNumber = useNumberFormat();\n const pickedText =\n picked &&\n (picked.count === 1\n ? picked.firstName\n : (pickedFilesLabel?.(picked.count) ??\n `${formatNumber ? formatNumber(picked.count) : picked.count} files`));\n const shownName = fileName ?? pickedText;\n\n const insideField = useInsideField();\n const insideControl = useInsideControl();\n const ownField = rendersOwnField('File', {\n insideField,\n insideControl,\n label,\n message,\n horizontal,\n fieldClassName,\n });\n const { controlId, fieldLabelProps } = useAutoLabelId({\n label,\n id: props.id,\n labelProps,\n rendersLabel: ownField,\n });\n const { classPrefix } = useConfig();\n // The input takes `controlId`, so `id` stays out of the spread, where an\n // undefined `id` key would wipe the id the label points at.\n const { id: _id, ...inputProps } = props;\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color,\n ...inputProps,\n });\n\n // Mutually exclusive alignment\n let alignmentClass: string | undefined;\n if (isRight && isCentered) {\n // If both are set, prefer isRight and warn in dev\n alignmentClass = prefixedClassNames(classPrefix, 'is-right');\n } else if (isRight) {\n alignmentClass = prefixedClassNames(classPrefix, 'is-right');\n } else if (isCentered) {\n alignmentClass = prefixedClassNames(classPrefix, 'is-centered');\n }\n\n const mainClass = usePrefixedClassNames('file', {\n [`is-${color}`]: !!color,\n [`is-${size}`]: !!size,\n 'is-boxed': isBoxed,\n 'is-fullwidth': isFullwidth ?? isFullWidth,\n 'has-name': hasName,\n 'is-empty': hasName && !shownName,\n });\n const fileClass = classNames(\n mainClass,\n bulmaHelperClasses,\n alignmentClass,\n className\n );\n\n const helpClass = usePrefixedClassNames('help', {\n [`is-${messageColor}`]: !!messageColor,\n });\n const messageEl = message ? <p className={helpClass}>{message}</p> : null;\n\n const fileElement = (\n <div className={fileClass}>\n <label className={usePrefixedClassNames('file-label')}>\n <input\n ref={ref}\n className={classNames(\n usePrefixedClassNames('file-input'),\n inputClassName\n )}\n type=\"file\"\n id={controlId}\n onChange={handleChange}\n {...rest}\n />\n <span className={usePrefixedClassNames('file-cta')}>\n {iconLeft && (\n <span className={prefixedClassNames(classPrefix, 'file-icon')}>\n {iconLeft}\n </span>\n )}\n <span className={usePrefixedClassNames('file-label')}>\n {buttonLabel || 'Choose a file\\u2026'}\n </span>\n {iconRight && (\n <span className={prefixedClassNames(classPrefix, 'file-icon')}>\n {iconRight}\n </span>\n )}\n </span>\n {hasName && shownName && (\n <span className={prefixedClassNames(classPrefix, 'file-name')}>\n {shownName}\n </span>\n )}\n </label>\n </div>\n );\n\n if (ownField) {\n return (\n <Field\n label={label}\n labelSize={labelSize}\n labelProps={fieldLabelProps}\n horizontal={horizontal}\n className={fieldClassName}\n >\n {fileElement}\n {messageEl}\n </Field>\n );\n }\n\n return (\n <>\n {fileElement}\n {messageEl}\n </>\n );\n }\n);\n\nFile.displayName = 'File';\n\nexport default File;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
@@ -0,0 +1,292 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ // INTERNAL: deliberately not exported from src/index.ts. Reading, converting
3
+ // and writing the CSS colors the color picker takes and gives.
4
+ //
5
+ // The picker works in HSV (hue, saturation and value, which the square
6
+ // calls brightness), since its square is saturation by brightness. Every
7
+ // color it reads becomes HSV, and every value it writes comes out of HSV in
8
+ // the format asked for.
9
+ const clamp$3 = (value, low, high) => Math.min(Math.max(value, low), high);
10
+ /** Rounds to a number of decimal places, so `0.1 + 0.2` writes as `0.3`. */
11
+ const round = (value, places) => {
12
+ const factor = 10 ** places;
13
+ return Math.round(value * factor) / factor;
14
+ };
15
+ // ---------------------------------------------------------------------------
16
+ // Conversions
17
+ // ---------------------------------------------------------------------------
18
+ /** HSV to RGB, each channel unrounded. */
19
+ function hsvaToRgba({ h, s, v, a }) {
20
+ const sat = s / 100;
21
+ const val = v / 100;
22
+ const at = (n) => {
23
+ const k = (n + h / 60) % 6;
24
+ return val - val * sat * Math.max(0, Math.min(k, 4 - k, 1));
25
+ };
26
+ return { r: at(5) * 255, g: at(3) * 255, b: at(1) * 255, a };
27
+ }
28
+ /** RGB to HSV. A gray has no hue, so it gets 0. */
29
+ function rgbaToHsva({ r, g, b, a }) {
30
+ const red = r / 255;
31
+ const green = g / 255;
32
+ const blue = b / 255;
33
+ const max = Math.max(red, green, blue);
34
+ const delta = max - Math.min(red, green, blue);
35
+ let h = 0;
36
+ if (delta > 0) {
37
+ if (max === red)
38
+ h = ((green - blue) / delta) % 6;
39
+ else if (max === green)
40
+ h = (blue - red) / delta + 2;
41
+ else
42
+ h = (red - green) / delta + 4;
43
+ }
44
+ return {
45
+ h: (h * 60 + 360) % 360,
46
+ s: max === 0 ? 0 : (delta / max) * 100,
47
+ v: max * 100,
48
+ a,
49
+ };
50
+ }
51
+ /** HSL (saturation and lightness from 0 to 100) to HSV, keeping the hue. */
52
+ function hslaToHsva(h, s, l, a) {
53
+ const sat = s / 100;
54
+ const light = l / 100;
55
+ const v = light + sat * Math.min(light, 1 - light);
56
+ return { h, s: v === 0 ? 0 : 2 * (1 - light / v) * 100, v: v * 100, a };
57
+ }
58
+ /** HSV to HSL, saturation and lightness from 0 to 100, keeping the hue. */
59
+ function hsvaToHsla({ h, s, v, a }) {
60
+ const sat = s / 100;
61
+ const val = v / 100;
62
+ const light = val * (1 - sat / 2);
63
+ const lowest = Math.min(light, 1 - light);
64
+ return {
65
+ h,
66
+ s: lowest === 0 ? 0 : ((val - light) / lowest) * 100,
67
+ l: light * 100,
68
+ a,
69
+ };
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Writing
73
+ // ---------------------------------------------------------------------------
74
+ const hexByte = (value) => Math.round(clamp$3(value, 0, 255))
75
+ .toString(16)
76
+ .padStart(2, '0');
77
+ /** Alpha as CSS writes it, to two decimal places: `0.5`, `1`. */
78
+ const writeAlpha = (a) => String(round(clamp$3(a, 0, 1), 2));
79
+ /**
80
+ * Writes a color in a format: `hsl(171, 100%, 41%)`, `#00d1b2` or
81
+ * `rgb(0, 209, 178)`, with alpha as `hsla(…)`, `#00d1b2ff` and `rgba(…)`.
82
+ * Channels are whole numbers, and the legacy comma form is used, which every
83
+ * CSS parser and color library reads.
84
+ */
85
+ function formatColor(color, format, alpha) {
86
+ if (format === 'hsl') {
87
+ const { h, s, l, a } = hsvaToHsla(color);
88
+ const channels = `${Math.round(h) % 360}, ${Math.round(s)}%, ${Math.round(l)}%`;
89
+ return alpha ? `hsla(${channels}, ${writeAlpha(a)})` : `hsl(${channels})`;
90
+ }
91
+ const { r, g, b, a } = hsvaToRgba(color);
92
+ if (format === 'hex') {
93
+ const hex = `#${hexByte(r)}${hexByte(g)}${hexByte(b)}`;
94
+ return alpha ? `${hex}${hexByte(a * 255)}` : hex;
95
+ }
96
+ const channels = `${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}`;
97
+ return alpha ? `rgba(${channels}, ${writeAlpha(a)})` : `rgb(${channels})`;
98
+ }
99
+ /** The color as 8-bit RGB and alpha to two places, for telling colors apart. */
100
+ const key = (color) => {
101
+ const { r, g, b, a } = hsvaToRgba(color);
102
+ return [r, g, b].map(c => Math.round(c)).join(',') + `,${writeAlpha(a)}`;
103
+ };
104
+ /** Whether two colors are the same once written as 8-bit RGB with alpha. */
105
+ const sameColor = (one, other) => key(one) === key(other);
106
+ /**
107
+ * The relative luminance of a color, from 0 for black to 1 for white, as
108
+ * WCAG defines it.
109
+ */
110
+ function luminance(color) {
111
+ const { r, g, b } = hsvaToRgba(color);
112
+ const linear = (channel) => {
113
+ const c = channel / 255;
114
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
115
+ };
116
+ return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
117
+ }
118
+ /**
119
+ * Whether black reads better than white on a color, by WCAG contrast
120
+ * ratio. Used for the check on a chosen swatch.
121
+ */
122
+ function prefersDarkInk(color) {
123
+ const l = luminance(color);
124
+ return (l + 0.05) / 0.05 >= 1.05 / (l + 0.05);
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Reading
128
+ // ---------------------------------------------------------------------------
129
+ // The CSS named colors, each name followed by its six hex digits.
130
+ const NAMED = 'aliceblue:f0f8ff,antiquewhite:faebd7,aqua:00ffff,aquamarine:7fffd4,' +
131
+ 'azure:f0ffff,beige:f5f5dc,bisque:ffe4c4,black:000000,' +
132
+ 'blanchedalmond:ffebcd,blue:0000ff,blueviolet:8a2be2,brown:a52a2a,' +
133
+ 'burlywood:deb887,cadetblue:5f9ea0,chartreuse:7fff00,chocolate:d2691e,' +
134
+ 'coral:ff7f50,cornflowerblue:6495ed,cornsilk:fff8dc,crimson:dc143c,' +
135
+ 'cyan:00ffff,darkblue:00008b,darkcyan:008b8b,darkgoldenrod:b8860b,' +
136
+ 'darkgray:a9a9a9,darkgreen:006400,darkgrey:a9a9a9,darkkhaki:bdb76b,' +
137
+ 'darkmagenta:8b008b,darkolivegreen:556b2f,darkorange:ff8c00,' +
138
+ 'darkorchid:9932cc,darkred:8b0000,darksalmon:e9967a,' +
139
+ 'darkseagreen:8fbc8f,darkslateblue:483d8b,darkslategray:2f4f4f,' +
140
+ 'darkslategrey:2f4f4f,darkturquoise:00ced1,darkviolet:9400d3,' +
141
+ 'deeppink:ff1493,deepskyblue:00bfff,dimgray:696969,dimgrey:696969,' +
142
+ 'dodgerblue:1e90ff,firebrick:b22222,floralwhite:fffaf0,' +
143
+ 'forestgreen:228b22,fuchsia:ff00ff,gainsboro:dcdcdc,' +
144
+ 'ghostwhite:f8f8ff,gold:ffd700,goldenrod:daa520,gray:808080,' +
145
+ 'green:008000,greenyellow:adff2f,grey:808080,honeydew:f0fff0,' +
146
+ 'hotpink:ff69b4,indianred:cd5c5c,indigo:4b0082,ivory:fffff0,' +
147
+ 'khaki:f0e68c,lavender:e6e6fa,lavenderblush:fff0f5,' +
148
+ 'lawngreen:7cfc00,lemonchiffon:fffacd,lightblue:add8e6,' +
149
+ 'lightcoral:f08080,lightcyan:e0ffff,lightgoldenrodyellow:fafad2,' +
150
+ 'lightgray:d3d3d3,lightgreen:90ee90,lightgrey:d3d3d3,' +
151
+ 'lightpink:ffb6c1,lightsalmon:ffa07a,lightseagreen:20b2aa,' +
152
+ 'lightskyblue:87cefa,lightslategray:778899,lightslategrey:778899,' +
153
+ 'lightsteelblue:b0c4de,lightyellow:ffffe0,lime:00ff00,' +
154
+ 'limegreen:32cd32,linen:faf0e6,magenta:ff00ff,maroon:800000,' +
155
+ 'mediumaquamarine:66cdaa,mediumblue:0000cd,mediumorchid:ba55d3,' +
156
+ 'mediumpurple:9370db,mediumseagreen:3cb371,mediumslateblue:7b68ee,' +
157
+ 'mediumspringgreen:00fa9a,mediumturquoise:48d1cc,' +
158
+ 'mediumvioletred:c71585,midnightblue:191970,mintcream:f5fffa,' +
159
+ 'mistyrose:ffe4e1,moccasin:ffe4b5,navajowhite:ffdead,navy:000080,' +
160
+ 'oldlace:fdf5e6,olive:808000,olivedrab:6b8e23,orange:ffa500,' +
161
+ 'orangered:ff4500,orchid:da70d6,palegoldenrod:eee8aa,' +
162
+ 'palegreen:98fb98,paleturquoise:afeeee,palevioletred:db7093,' +
163
+ 'papayawhip:ffefd5,peachpuff:ffdab9,peru:cd853f,pink:ffc0cb,' +
164
+ 'plum:dda0dd,powderblue:b0e0e6,purple:800080,' +
165
+ 'rebeccapurple:663399,red:ff0000,rosybrown:bc8f8f,' +
166
+ 'royalblue:4169e1,saddlebrown:8b4513,salmon:fa8072,' +
167
+ 'sandybrown:f4a460,seagreen:2e8b57,seashell:fff5ee,sienna:a0522d,' +
168
+ 'silver:c0c0c0,skyblue:87ceeb,slateblue:6a5acd,slategray:708090,' +
169
+ 'slategrey:708090,snow:fffafa,springgreen:00ff7f,steelblue:4682b4,' +
170
+ 'tan:d2b48c,teal:008080,thistle:d8bfd8,tomato:ff6347,' +
171
+ 'turquoise:40e0d0,violet:ee82ee,wheat:f5deb3,white:ffffff,' +
172
+ 'whitesmoke:f5f5f5,yellow:ffff00,yellowgreen:9acd32';
173
+ let namedColors;
174
+ /** The hex digits of a CSS color name, or undefined for any other word. */
175
+ const namedHex = (name) => {
176
+ if (!namedColors) {
177
+ namedColors = new Map(NAMED.split(',').map(entry => entry.split(':')));
178
+ }
179
+ return namedColors.get(name);
180
+ };
181
+ /** Reads `#rgb`, `#rgba`, `#rrggbb` and `#rrggbbaa`, the `#` optional. */
182
+ function parseHex(text) {
183
+ const digits = text.startsWith('#') ? text.slice(1) : text;
184
+ if (!/^[0-9a-f]+$/.test(digits) || ![3, 4, 6, 8].includes(digits.length)) {
185
+ return null;
186
+ }
187
+ const full = digits.length < 6
188
+ ? digits
189
+ .split('')
190
+ .map(d => d + d)
191
+ .join('')
192
+ : digits;
193
+ const byte = (i) => parseInt(full.slice(i, i + 2), 16);
194
+ return rgbaToHsva({
195
+ r: byte(0),
196
+ g: byte(2),
197
+ b: byte(4),
198
+ a: full.length === 8 ? byte(6) / 255 : 1,
199
+ });
200
+ }
201
+ /**
202
+ * The arguments of a CSS color function, in either syntax: commas
203
+ * (`rgb(0, 209, 178)`) or spaces with the alpha after a slash
204
+ * (`rgb(0 209 178 / 50%)`). Null when they're neither.
205
+ */
206
+ function splitArguments(body) {
207
+ if (body.includes(',')) {
208
+ const parts = body.split(',').map(part => part.trim());
209
+ return parts.includes('') ? null : parts;
210
+ }
211
+ const [channels, alpha, ...more] = body.split('/');
212
+ const parts = channels.trim().split(/\s+/).filter(Boolean);
213
+ if (more.length > 0 || parts.length !== 3)
214
+ return null;
215
+ if (alpha === undefined)
216
+ return parts;
217
+ const a = alpha.trim();
218
+ return a && !/\s/.test(a) ? [...parts, a] : null;
219
+ }
220
+ /** A plain number or a percentage of `whole`, or NaN. */
221
+ const numberOf = (part, whole) => {
222
+ if (!/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?%?$/.test(part))
223
+ return NaN;
224
+ const value = parseFloat(part);
225
+ return part.endsWith('%') ? (value / 100) * whole : value;
226
+ };
227
+ /** An alpha, `0.5` or `50%`, clamped to 0 to 1, or NaN. */
228
+ const alphaOf = (part) => part === undefined ? 1 : clamp$3(numberOf(part, 1), 0, 1);
229
+ const ANGLE_UNITS = {
230
+ deg: 1,
231
+ grad: 360 / 400,
232
+ rad: 180 / Math.PI,
233
+ turn: 360,
234
+ };
235
+ /** A hue in degrees from 0 to 360, from a number or an angle, or NaN. */
236
+ const hueOf = (part) => {
237
+ const match = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(deg|grad|rad|turn)?$/.exec(part);
238
+ if (!match)
239
+ return NaN;
240
+ const degrees = parseFloat(match[1]) * ANGLE_UNITS[match[2] ?? 'deg'];
241
+ return ((degrees % 360) + 360) % 360;
242
+ };
243
+ /** Reads `rgb()`, `rgba()`, `hsl()` and `hsla()` in either syntax. */
244
+ function parseFunction(text) {
245
+ const match = /^(rgba?|hsla?)\((.*)\)$/.exec(text);
246
+ if (!match)
247
+ return null;
248
+ const parts = splitArguments(match[2]);
249
+ if (!parts || parts.length < 3 || parts.length > 4)
250
+ return null;
251
+ const [first, second, third, fourth] = parts;
252
+ const a = alphaOf(fourth);
253
+ if (match[1].startsWith('rgb')) {
254
+ const [r, g, b] = [first, second, third].map(part => clamp$3(numberOf(part, 255), 0, 255));
255
+ if ([r, g, b, a].some(Number.isNaN))
256
+ return null;
257
+ return rgbaToHsva({ r, g, b, a });
258
+ }
259
+ const h = hueOf(first);
260
+ // Modern syntax lets saturation and lightness drop the percent sign.
261
+ const [s, l] = [second, third].map(part => clamp$3(numberOf(part.replace(/%$/, ''), 100), 0, 100));
262
+ if ([h, s, l, a].some(Number.isNaN))
263
+ return null;
264
+ return hslaToHsva(h, s, l, a);
265
+ }
266
+ /**
267
+ * Whether a color's text names its own hue, as `hsl()` and `hsla()` do even
268
+ * for a gray, where hex, `rgb()` and the color names leave it out.
269
+ */
270
+ const namesHue = (text) => /^\s*hsla?\(/i.test(text);
271
+ /**
272
+ * Reads a CSS color: hex (with or without `#`), `rgb()`, `rgba()`, `hsl()`
273
+ * and `hsla()` in either syntax, the named colors, and `transparent`. Null
274
+ * for anything else, such as `oklch()` or `var(--x)`, which only the
275
+ * browser can resolve.
276
+ */
277
+ function parseColor(input) {
278
+ if (!input)
279
+ return null;
280
+ const text = input.trim().toLowerCase();
281
+ if (text === 'transparent')
282
+ return { h: 0, s: 0, v: 0, a: 0 };
283
+ const named = namedHex(text);
284
+ if (named)
285
+ return parseHex(named);
286
+ if (text.includes('('))
287
+ return parseFunction(text);
288
+ return parseHex(text);
289
+ }
290
+
291
+ export { formatColor, hslaToHsva, hsvaToHsla, hsvaToRgba, luminance, namesHue, parseColor, prefersDarkInk, rgbaToHsva, sameColor };
292
+ //# sourceMappingURL=colorMath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colorMath.js","sources":["../../../../src/form/_colorInternals/colorMath.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts. Reading, converting\n// and writing the CSS colors the color picker takes and gives.\n//\n// The picker works in HSV (hue, saturation and value, which the square\n// calls brightness), since its square is saturation by brightness. Every\n// color it reads becomes HSV, and every value it writes comes out of HSV in\n// the format asked for.\n\n/** The formats a color picker writes its value in. */\nexport type ColorFormat = 'hsl' | 'hex' | 'rgb';\n\n/**\n * A color as the picker holds it: hue in degrees from 0 to 360, saturation\n * and value (brightness) from 0 to 100, and alpha from 0 to 1.\n */\nexport interface Hsva {\n h: number;\n s: number;\n v: number;\n a: number;\n}\n\n/** Red, green and blue from 0 to 255, and alpha from 0 to 1. */\nexport interface Rgba {\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\nconst clamp = (value: number, low: number, high: number): number =>\n Math.min(Math.max(value, low), high);\n\n/** Rounds to a number of decimal places, so `0.1 + 0.2` writes as `0.3`. */\nconst round = (value: number, places: number): number => {\n const factor = 10 ** places;\n return Math.round(value * factor) / factor;\n};\n\n// ---------------------------------------------------------------------------\n// Conversions\n// ---------------------------------------------------------------------------\n\n/** HSV to RGB, each channel unrounded. */\nexport function hsvaToRgba({ h, s, v, a }: Hsva): Rgba {\n const sat = s / 100;\n const val = v / 100;\n const at = (n: number) => {\n const k = (n + h / 60) % 6;\n return val - val * sat * Math.max(0, Math.min(k, 4 - k, 1));\n };\n return { r: at(5) * 255, g: at(3) * 255, b: at(1) * 255, a };\n}\n\n/** RGB to HSV. A gray has no hue, so it gets 0. */\nexport function rgbaToHsva({ r, g, b, a }: Rgba): Hsva {\n const red = r / 255;\n const green = g / 255;\n const blue = b / 255;\n const max = Math.max(red, green, blue);\n const delta = max - Math.min(red, green, blue);\n let h = 0;\n if (delta > 0) {\n if (max === red) h = ((green - blue) / delta) % 6;\n else if (max === green) h = (blue - red) / delta + 2;\n else h = (red - green) / delta + 4;\n }\n return {\n h: (h * 60 + 360) % 360,\n s: max === 0 ? 0 : (delta / max) * 100,\n v: max * 100,\n a,\n };\n}\n\n/** HSL (saturation and lightness from 0 to 100) to HSV, keeping the hue. */\nexport function hslaToHsva(h: number, s: number, l: number, a: number): Hsva {\n const sat = s / 100;\n const light = l / 100;\n const v = light + sat * Math.min(light, 1 - light);\n return { h, s: v === 0 ? 0 : 2 * (1 - light / v) * 100, v: v * 100, a };\n}\n\n/** HSV to HSL, saturation and lightness from 0 to 100, keeping the hue. */\nexport function hsvaToHsla({ h, s, v, a }: Hsva): {\n h: number;\n s: number;\n l: number;\n a: number;\n} {\n const sat = s / 100;\n const val = v / 100;\n const light = val * (1 - sat / 2);\n const lowest = Math.min(light, 1 - light);\n return {\n h,\n s: lowest === 0 ? 0 : ((val - light) / lowest) * 100,\n l: light * 100,\n a,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Writing\n// ---------------------------------------------------------------------------\n\nconst hexByte = (value: number) =>\n Math.round(clamp(value, 0, 255))\n .toString(16)\n .padStart(2, '0');\n\n/** Alpha as CSS writes it, to two decimal places: `0.5`, `1`. */\nconst writeAlpha = (a: number) => String(round(clamp(a, 0, 1), 2));\n\n/**\n * Writes a color in a format: `hsl(171, 100%, 41%)`, `#00d1b2` or\n * `rgb(0, 209, 178)`, with alpha as `hsla(…)`, `#00d1b2ff` and `rgba(…)`.\n * Channels are whole numbers, and the legacy comma form is used, which every\n * CSS parser and color library reads.\n */\nexport function formatColor(\n color: Hsva,\n format: ColorFormat,\n alpha: boolean\n): string {\n if (format === 'hsl') {\n const { h, s, l, a } = hsvaToHsla(color);\n const channels = `${Math.round(h) % 360}, ${Math.round(s)}%, ${Math.round(l)}%`;\n return alpha ? `hsla(${channels}, ${writeAlpha(a)})` : `hsl(${channels})`;\n }\n const { r, g, b, a } = hsvaToRgba(color);\n if (format === 'hex') {\n const hex = `#${hexByte(r)}${hexByte(g)}${hexByte(b)}`;\n return alpha ? `${hex}${hexByte(a * 255)}` : hex;\n }\n const channels = `${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}`;\n return alpha ? `rgba(${channels}, ${writeAlpha(a)})` : `rgb(${channels})`;\n}\n\n/** The color as 8-bit RGB and alpha to two places, for telling colors apart. */\nconst key = (color: Hsva): string => {\n const { r, g, b, a } = hsvaToRgba(color);\n return [r, g, b].map(c => Math.round(c)).join(',') + `,${writeAlpha(a)}`;\n};\n\n/** Whether two colors are the same once written as 8-bit RGB with alpha. */\nexport const sameColor = (one: Hsva, other: Hsva): boolean =>\n key(one) === key(other);\n\n/**\n * The relative luminance of a color, from 0 for black to 1 for white, as\n * WCAG defines it.\n */\nexport function luminance(color: Hsva): number {\n const { r, g, b } = hsvaToRgba(color);\n const linear = (channel: number) => {\n const c = channel / 255;\n return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);\n}\n\n/**\n * Whether black reads better than white on a color, by WCAG contrast\n * ratio. Used for the check on a chosen swatch.\n */\nexport function prefersDarkInk(color: Hsva): boolean {\n const l = luminance(color);\n return (l + 0.05) / 0.05 >= 1.05 / (l + 0.05);\n}\n\n// ---------------------------------------------------------------------------\n// Reading\n// ---------------------------------------------------------------------------\n\n// The CSS named colors, each name followed by its six hex digits.\nconst NAMED =\n 'aliceblue:f0f8ff,antiquewhite:faebd7,aqua:00ffff,aquamarine:7fffd4,' +\n 'azure:f0ffff,beige:f5f5dc,bisque:ffe4c4,black:000000,' +\n 'blanchedalmond:ffebcd,blue:0000ff,blueviolet:8a2be2,brown:a52a2a,' +\n 'burlywood:deb887,cadetblue:5f9ea0,chartreuse:7fff00,chocolate:d2691e,' +\n 'coral:ff7f50,cornflowerblue:6495ed,cornsilk:fff8dc,crimson:dc143c,' +\n 'cyan:00ffff,darkblue:00008b,darkcyan:008b8b,darkgoldenrod:b8860b,' +\n 'darkgray:a9a9a9,darkgreen:006400,darkgrey:a9a9a9,darkkhaki:bdb76b,' +\n 'darkmagenta:8b008b,darkolivegreen:556b2f,darkorange:ff8c00,' +\n 'darkorchid:9932cc,darkred:8b0000,darksalmon:e9967a,' +\n 'darkseagreen:8fbc8f,darkslateblue:483d8b,darkslategray:2f4f4f,' +\n 'darkslategrey:2f4f4f,darkturquoise:00ced1,darkviolet:9400d3,' +\n 'deeppink:ff1493,deepskyblue:00bfff,dimgray:696969,dimgrey:696969,' +\n 'dodgerblue:1e90ff,firebrick:b22222,floralwhite:fffaf0,' +\n 'forestgreen:228b22,fuchsia:ff00ff,gainsboro:dcdcdc,' +\n 'ghostwhite:f8f8ff,gold:ffd700,goldenrod:daa520,gray:808080,' +\n 'green:008000,greenyellow:adff2f,grey:808080,honeydew:f0fff0,' +\n 'hotpink:ff69b4,indianred:cd5c5c,indigo:4b0082,ivory:fffff0,' +\n 'khaki:f0e68c,lavender:e6e6fa,lavenderblush:fff0f5,' +\n 'lawngreen:7cfc00,lemonchiffon:fffacd,lightblue:add8e6,' +\n 'lightcoral:f08080,lightcyan:e0ffff,lightgoldenrodyellow:fafad2,' +\n 'lightgray:d3d3d3,lightgreen:90ee90,lightgrey:d3d3d3,' +\n 'lightpink:ffb6c1,lightsalmon:ffa07a,lightseagreen:20b2aa,' +\n 'lightskyblue:87cefa,lightslategray:778899,lightslategrey:778899,' +\n 'lightsteelblue:b0c4de,lightyellow:ffffe0,lime:00ff00,' +\n 'limegreen:32cd32,linen:faf0e6,magenta:ff00ff,maroon:800000,' +\n 'mediumaquamarine:66cdaa,mediumblue:0000cd,mediumorchid:ba55d3,' +\n 'mediumpurple:9370db,mediumseagreen:3cb371,mediumslateblue:7b68ee,' +\n 'mediumspringgreen:00fa9a,mediumturquoise:48d1cc,' +\n 'mediumvioletred:c71585,midnightblue:191970,mintcream:f5fffa,' +\n 'mistyrose:ffe4e1,moccasin:ffe4b5,navajowhite:ffdead,navy:000080,' +\n 'oldlace:fdf5e6,olive:808000,olivedrab:6b8e23,orange:ffa500,' +\n 'orangered:ff4500,orchid:da70d6,palegoldenrod:eee8aa,' +\n 'palegreen:98fb98,paleturquoise:afeeee,palevioletred:db7093,' +\n 'papayawhip:ffefd5,peachpuff:ffdab9,peru:cd853f,pink:ffc0cb,' +\n 'plum:dda0dd,powderblue:b0e0e6,purple:800080,' +\n 'rebeccapurple:663399,red:ff0000,rosybrown:bc8f8f,' +\n 'royalblue:4169e1,saddlebrown:8b4513,salmon:fa8072,' +\n 'sandybrown:f4a460,seagreen:2e8b57,seashell:fff5ee,sienna:a0522d,' +\n 'silver:c0c0c0,skyblue:87ceeb,slateblue:6a5acd,slategray:708090,' +\n 'slategrey:708090,snow:fffafa,springgreen:00ff7f,steelblue:4682b4,' +\n 'tan:d2b48c,teal:008080,thistle:d8bfd8,tomato:ff6347,' +\n 'turquoise:40e0d0,violet:ee82ee,wheat:f5deb3,white:ffffff,' +\n 'whitesmoke:f5f5f5,yellow:ffff00,yellowgreen:9acd32';\n\nlet namedColors: Map<string, string> | undefined;\n\n/** The hex digits of a CSS color name, or undefined for any other word. */\nconst namedHex = (name: string): string | undefined => {\n if (!namedColors) {\n namedColors = new Map(\n NAMED.split(',').map(entry => entry.split(':') as [string, string])\n );\n }\n return namedColors.get(name);\n};\n\n/** Reads `#rgb`, `#rgba`, `#rrggbb` and `#rrggbbaa`, the `#` optional. */\nfunction parseHex(text: string): Hsva | null {\n const digits = text.startsWith('#') ? text.slice(1) : text;\n if (!/^[0-9a-f]+$/.test(digits) || ![3, 4, 6, 8].includes(digits.length)) {\n return null;\n }\n const full =\n digits.length < 6\n ? digits\n .split('')\n .map(d => d + d)\n .join('')\n : digits;\n const byte = (i: number) => parseInt(full.slice(i, i + 2), 16);\n return rgbaToHsva({\n r: byte(0),\n g: byte(2),\n b: byte(4),\n a: full.length === 8 ? byte(6) / 255 : 1,\n });\n}\n\n/**\n * The arguments of a CSS color function, in either syntax: commas\n * (`rgb(0, 209, 178)`) or spaces with the alpha after a slash\n * (`rgb(0 209 178 / 50%)`). Null when they're neither.\n */\nfunction splitArguments(body: string): string[] | null {\n if (body.includes(',')) {\n const parts = body.split(',').map(part => part.trim());\n return parts.includes('') ? null : parts;\n }\n const [channels, alpha, ...more] = body.split('/');\n const parts = channels.trim().split(/\\s+/).filter(Boolean);\n if (more.length > 0 || parts.length !== 3) return null;\n if (alpha === undefined) return parts;\n const a = alpha.trim();\n return a && !/\\s/.test(a) ? [...parts, a] : null;\n}\n\n/** A plain number or a percentage of `whole`, or NaN. */\nconst numberOf = (part: string, whole: number): number => {\n if (!/^[+-]?(\\d+\\.?\\d*|\\.\\d+)(e[+-]?\\d+)?%?$/.test(part)) return NaN;\n const value = parseFloat(part);\n return part.endsWith('%') ? (value / 100) * whole : value;\n};\n\n/** An alpha, `0.5` or `50%`, clamped to 0 to 1, or NaN. */\nconst alphaOf = (part: string | undefined): number =>\n part === undefined ? 1 : clamp(numberOf(part, 1), 0, 1);\n\nconst ANGLE_UNITS: Record<string, number> = {\n deg: 1,\n grad: 360 / 400,\n rad: 180 / Math.PI,\n turn: 360,\n};\n\n/** A hue in degrees from 0 to 360, from a number or an angle, or NaN. */\nconst hueOf = (part: string): number => {\n const match =\n /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:e[+-]?\\d+)?)(deg|grad|rad|turn)?$/.exec(part);\n if (!match) return NaN;\n const degrees = parseFloat(match[1]) * ANGLE_UNITS[match[2] ?? 'deg'];\n return ((degrees % 360) + 360) % 360;\n};\n\n/** Reads `rgb()`, `rgba()`, `hsl()` and `hsla()` in either syntax. */\nfunction parseFunction(text: string): Hsva | null {\n const match = /^(rgba?|hsla?)\\((.*)\\)$/.exec(text);\n if (!match) return null;\n const parts = splitArguments(match[2]);\n if (!parts || parts.length < 3 || parts.length > 4) return null;\n const [first, second, third, fourth] = parts;\n const a = alphaOf(fourth);\n if (match[1].startsWith('rgb')) {\n const [r, g, b] = [first, second, third].map(part =>\n clamp(numberOf(part, 255), 0, 255)\n );\n if ([r, g, b, a].some(Number.isNaN)) return null;\n return rgbaToHsva({ r, g, b, a });\n }\n const h = hueOf(first);\n // Modern syntax lets saturation and lightness drop the percent sign.\n const [s, l] = [second, third].map(part =>\n clamp(numberOf(part.replace(/%$/, ''), 100), 0, 100)\n );\n if ([h, s, l, a].some(Number.isNaN)) return null;\n return hslaToHsva(h, s, l, a);\n}\n\n/**\n * Whether a color's text names its own hue, as `hsl()` and `hsla()` do even\n * for a gray, where hex, `rgb()` and the color names leave it out.\n */\nexport const namesHue = (text: string): boolean => /^\\s*hsla?\\(/i.test(text);\n\n/**\n * Reads a CSS color: hex (with or without `#`), `rgb()`, `rgba()`, `hsl()`\n * and `hsla()` in either syntax, the named colors, and `transparent`. Null\n * for anything else, such as `oklch()` or `var(--x)`, which only the\n * browser can resolve.\n */\nexport function parseColor(input: string | null | undefined): Hsva | null {\n if (!input) return null;\n const text = input.trim().toLowerCase();\n if (text === 'transparent') return { h: 0, s: 0, v: 0, a: 0 };\n const named = namedHex(text);\n if (named) return parseHex(named);\n if (text.includes('(')) return parseFunction(text);\n return parseHex(text);\n}\n"],"names":["clamp"],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ import { createContext, useContext, useEffect, useState, useRef, useCallback } from 'react';
3
+ import { useNumberFormat } from '../../helpers/localeFormat.js';
4
+ import { useClientLayoutEffect } from '../../helpers/useClientLayoutEffect.js';
5
+ import { warnOnce } from '../../helpers/devWarnings.js';
6
+ import { useNativeMobilePicker } from '../_pickerInternals/useNativeMobilePicker.js';
7
+ import { parseColor, formatColor, namesHue } from './colorMath.js';
8
+
9
+ // INTERNAL: deliberately not exported from src/index.ts. The color a picker
10
+ // holds and the context its parts read, shared by ColorPicker and the panel
11
+ // ColorInput opens, so the panel needs no Field or Control of its own.
12
+ const DEFAULT_LABELS = {
13
+ area: 'Saturation and brightness',
14
+ areaRoleDescription: '2D slider',
15
+ saturation: 'Saturation',
16
+ brightness: 'Brightness',
17
+ hue: 'Hue',
18
+ alpha: 'Alpha',
19
+ degrees: 'degrees',
20
+ value: 'Color value',
21
+ swatches: 'Swatches',
22
+ eyeDropper: 'Pick a color from the screen',
23
+ };
24
+ /** The labels with each one left out, or empty, at its default. */
25
+ const mergeColorLabels = (labels) => {
26
+ const merged = { ...DEFAULT_LABELS };
27
+ for (const [key, value] of Object.entries(labels ?? {})) {
28
+ if (value)
29
+ merged[key] = value;
30
+ }
31
+ return merged;
32
+ };
33
+ const BLACK = { h: 0, s: 0, v: 0, a: 1 };
34
+ /**
35
+ * A color read from outside the picker, written as `source`, with the hue
36
+ * and saturation the picker had where the new color has none, so the thumbs
37
+ * don't jump to 0. A gray written as hex, `rgb()` or a name has no hue, so
38
+ * it keeps the picker's; one written as `hsl()` names its hue, which wins.
39
+ * Black has no saturation in the square either way, and keeps the picker's,
40
+ * which writes no differently.
41
+ */
42
+ const keepingChannels = (next, previous, source) => ({
43
+ ...next,
44
+ h: (next.s === 0 || next.v === 0) && !namesHue(source) ? previous.h : next.h,
45
+ s: next.v === 0 ? previous.s : next.s,
46
+ });
47
+ const ColorPickerContext = createContext(null);
48
+ /** Does nothing, for a part outside a picker and a cancelled eye dropper. */
49
+ const noop = () => { };
50
+ // What a part outside a ColorPicker reads, so its hooks run the same; it
51
+ // then renders nothing.
52
+ const standalone = {
53
+ color: BLACK,
54
+ text: '',
55
+ format: 'hsl',
56
+ alpha: false,
57
+ disabled: true,
58
+ labels: DEFAULT_LABELS,
59
+ swatches: undefined,
60
+ eyeDropper: false,
61
+ touch: false,
62
+ percent: String,
63
+ degrees: String,
64
+ change: noop,
65
+ end: noop,
66
+ };
67
+ /** The picker around a part, or null with a development warning. */
68
+ function usePicker(part) {
69
+ const picker = useContext(ColorPickerContext);
70
+ const outside = !picker;
71
+ useEffect(() => {
72
+ if (outside) {
73
+ warnOnce(`ColorPicker:${part}:outside`, `[bestax-bulma] <ColorPicker.${part}> needs a <ColorPicker> around ` +
74
+ 'it, which holds the color, and renders nothing without one.');
75
+ }
76
+ }, [outside, part]);
77
+ return picker;
78
+ }
79
+ /** A percentage, in the provider's locale when there is one. */
80
+ const writePercent = (formatNumber) => (value) => formatNumber
81
+ ? formatNumber(value / 100, { style: 'percent' })
82
+ : `${value}%`;
83
+ /** Degrees, in the provider's locale when there is one, else with `unit`. */
84
+ const writeDegrees = (formatNumber, unit) => (value) => formatNumber
85
+ ? formatNumber(value, {
86
+ style: 'unit',
87
+ unit: 'degree',
88
+ unitDisplay: 'long',
89
+ })
90
+ : `${value} ${unit}`;
91
+ /**
92
+ * Holds a picker's color and builds what its parts read. Controlled, the
93
+ * color shown follows `value`; a value the picker handed out comes back as
94
+ * the exact color it was, thumbs and all.
95
+ */
96
+ function useColorPicker({ value, defaultValue, onChange, onChangeEnd, format, alpha, disabled, swatches, eyeDropper, labels, }) {
97
+ const isControlled = value !== undefined;
98
+ // The color the picker last set or started with, and its text. While
99
+ // `value` is that text, this is the color shown.
100
+ const [own, setOwn] = useState(() => {
101
+ const start = isControlled ? value : defaultValue;
102
+ return { text: start ?? '', color: parseColor(start) ?? BLACK };
103
+ });
104
+ // A controlled value from outside becomes the color held, during this
105
+ // render, keeping the hue and saturation a gray or black has none of.
106
+ // One the picker can't read changes nothing, so the last good color
107
+ // stays on screen.
108
+ if (isControlled && value !== own.text) {
109
+ const parsed = parseColor(value);
110
+ if (parsed) {
111
+ setOwn({
112
+ text: value,
113
+ color: keepingChannels(parsed, own.color, value),
114
+ });
115
+ }
116
+ }
117
+ const color = alpha ? own.color : { ...own.color, a: 1 };
118
+ const text = formatColor(color, format, alpha);
119
+ // The latest text handed out, so a step that writes the same text
120
+ // doesn't call onChange again, and a drag's end reports the last one.
121
+ const lastTextRef = useRef(text);
122
+ useClientLayoutEffect(() => {
123
+ lastTextRef.current = text;
124
+ });
125
+ const change = useCallback((next, finished = false) => {
126
+ if (disabled)
127
+ return;
128
+ const solid = alpha ? next : { ...next, a: 1 };
129
+ const nextText = formatColor(solid, format, alpha);
130
+ setOwn({ text: nextText, color: solid });
131
+ if (nextText !== lastTextRef.current) {
132
+ lastTextRef.current = nextText;
133
+ onChange?.(nextText);
134
+ }
135
+ if (finished)
136
+ onChangeEnd?.(nextText);
137
+ }, [disabled, alpha, format, onChange, onChangeEnd]);
138
+ const end = useCallback(() => onChangeEnd?.(lastTextRef.current), [onChangeEnd]);
139
+ const formatNumber = useNumberFormat();
140
+ const { isCoarsePointer } = useNativeMobilePicker();
141
+ const merged = mergeColorLabels(labels);
142
+ return {
143
+ color,
144
+ text,
145
+ format,
146
+ alpha,
147
+ disabled,
148
+ labels: merged,
149
+ swatches,
150
+ eyeDropper,
151
+ touch: isCoarsePointer,
152
+ percent: writePercent(formatNumber),
153
+ degrees: writeDegrees(formatNumber, merged.degrees),
154
+ change,
155
+ end,
156
+ };
157
+ }
158
+
159
+ export { BLACK, ColorPickerContext, keepingChannels, mergeColorLabels, noop, standalone, useColorPicker, usePicker };
160
+ //# sourceMappingURL=pickerState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickerState.js","sources":["../../../../src/form/_colorInternals/pickerState.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts. The color a picker\n// holds and the context its parts read, shared by ColorPicker and the panel\n// ColorInput opens, so the panel needs no Field or Control of its own.\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useNumberFormat, NumberFormatter } from '../../helpers/localeFormat';\nimport { useClientLayoutEffect } from '../../helpers/useClientLayoutEffect';\nimport { warnOnce } from '../../helpers/devWarnings';\nimport { useNativeMobilePicker } from '../_pickerInternals/useNativeMobilePicker';\nimport {\n ColorFormat,\n Hsva,\n formatColor,\n namesHue,\n parseColor,\n} from './colorMath';\nimport type { ColorPickerLabels, ColorPickerSwatch } from '../ColorPicker';\n\nconst DEFAULT_LABELS: Required<ColorPickerLabels> = {\n area: 'Saturation and brightness',\n areaRoleDescription: '2D slider',\n saturation: 'Saturation',\n brightness: 'Brightness',\n hue: 'Hue',\n alpha: 'Alpha',\n degrees: 'degrees',\n value: 'Color value',\n swatches: 'Swatches',\n eyeDropper: 'Pick a color from the screen',\n};\n\n/** The labels with each one left out, or empty, at its default. */\nexport const mergeColorLabels = (\n labels: ColorPickerLabels | undefined\n): Required<ColorPickerLabels> => {\n const merged = { ...DEFAULT_LABELS };\n for (const [key, value] of Object.entries(labels ?? {})) {\n if (value) merged[key as keyof ColorPickerLabels] = value;\n }\n return merged;\n};\n\nexport const BLACK: Hsva = { h: 0, s: 0, v: 0, a: 1 };\n\n/**\n * A color read from outside the picker, written as `source`, with the hue\n * and saturation the picker had where the new color has none, so the thumbs\n * don't jump to 0. A gray written as hex, `rgb()` or a name has no hue, so\n * it keeps the picker's; one written as `hsl()` names its hue, which wins.\n * Black has no saturation in the square either way, and keeps the picker's,\n * which writes no differently.\n */\nexport const keepingChannels = (\n next: Hsva,\n previous: Hsva,\n source: string\n): Hsva => ({\n ...next,\n h: (next.s === 0 || next.v === 0) && !namesHue(source) ? previous.h : next.h,\n s: next.v === 0 ? previous.s : next.s,\n});\n\n/** What a picker's parts read from the picker around them. */\nexport interface ColorPickerContextValue {\n /** The color shown, alpha at 1 unless the picker takes alpha. */\n color: Hsva;\n /** The color written in the picker's format. */\n text: string;\n format: ColorFormat;\n alpha: boolean;\n disabled: boolean;\n labels: Required<ColorPickerLabels>;\n swatches: ColorPickerSwatch[] | 'bulma' | undefined;\n eyeDropper: boolean;\n /** Whether a touch screen reader is likely, so both square inputs show. */\n touch: boolean;\n /** Writes a percentage, in the locale of a LocaleProvider when there is one. */\n percent: (value: number) => string;\n /** Writes a hue in degrees, in the locale of a LocaleProvider when there is one. */\n degrees: (value: number) => string;\n /** Sets the color: `onChange`, and `onChangeEnd` too when `end` is set. */\n change: (next: Hsva, end?: boolean) => void;\n /** Ends a drag: `onChangeEnd` with the latest value. */\n end: () => void;\n}\n\nexport const ColorPickerContext = createContext<ColorPickerContextValue | null>(\n null\n);\n\n/** Does nothing, for a part outside a picker and a cancelled eye dropper. */\nexport const noop = () => {};\n\n// What a part outside a ColorPicker reads, so its hooks run the same; it\n// then renders nothing.\nexport const standalone: ColorPickerContextValue = {\n color: BLACK,\n text: '',\n format: 'hsl',\n alpha: false,\n disabled: true,\n labels: DEFAULT_LABELS,\n swatches: undefined,\n eyeDropper: false,\n touch: false,\n percent: String,\n degrees: String,\n change: noop,\n end: noop,\n};\n\n/** The picker around a part, or null with a development warning. */\nexport function usePicker(part: string): ColorPickerContextValue | null {\n const picker = useContext(ColorPickerContext);\n const outside = !picker;\n useEffect(() => {\n if (outside) {\n warnOnce(\n `ColorPicker:${part}:outside`,\n `[bestax-bulma] <ColorPicker.${part}> needs a <ColorPicker> around ` +\n 'it, which holds the color, and renders nothing without one.'\n );\n }\n }, [outside, part]);\n return picker;\n}\n\n/** A percentage, in the provider's locale when there is one. */\nconst writePercent =\n (formatNumber: NumberFormatter | null) => (value: number) =>\n formatNumber\n ? formatNumber(value / 100, { style: 'percent' })\n : `${value}%`;\n\n/** Degrees, in the provider's locale when there is one, else with `unit`. */\nconst writeDegrees =\n (formatNumber: NumberFormatter | null, unit: string) => (value: number) =>\n formatNumber\n ? formatNumber(value, {\n style: 'unit',\n unit: 'degree',\n unitDisplay: 'long',\n })\n : `${value} ${unit}`;\n\n/** What a picker is given: ColorPicker's props, or the ones ColorInput passes on. */\nexport interface PickerOptions {\n value: string | undefined;\n defaultValue: string | undefined;\n onChange: ((value: string) => void) | undefined;\n onChangeEnd: ((value: string) => void) | undefined;\n format: ColorFormat;\n alpha: boolean;\n disabled: boolean;\n swatches: ColorPickerSwatch[] | 'bulma' | undefined;\n eyeDropper: boolean;\n labels: ColorPickerLabels | undefined;\n}\n\n/**\n * Holds a picker's color and builds what its parts read. Controlled, the\n * color shown follows `value`; a value the picker handed out comes back as\n * the exact color it was, thumbs and all.\n */\nexport function useColorPicker({\n value,\n defaultValue,\n onChange,\n onChangeEnd,\n format,\n alpha,\n disabled,\n swatches,\n eyeDropper,\n labels,\n}: PickerOptions): ColorPickerContextValue {\n const isControlled = value !== undefined;\n // The color the picker last set or started with, and its text. While\n // `value` is that text, this is the color shown.\n const [own, setOwn] = useState(() => {\n const start = isControlled ? value : defaultValue;\n return { text: start ?? '', color: parseColor(start) ?? BLACK };\n });\n // A controlled value from outside becomes the color held, during this\n // render, keeping the hue and saturation a gray or black has none of.\n // One the picker can't read changes nothing, so the last good color\n // stays on screen.\n if (isControlled && value !== own.text) {\n const parsed = parseColor(value);\n if (parsed) {\n setOwn({\n text: value,\n color: keepingChannels(parsed, own.color, value),\n });\n }\n }\n const color = alpha ? own.color : { ...own.color, a: 1 };\n const text = formatColor(color, format, alpha);\n\n // The latest text handed out, so a step that writes the same text\n // doesn't call onChange again, and a drag's end reports the last one.\n const lastTextRef = useRef(text);\n useClientLayoutEffect(() => {\n lastTextRef.current = text;\n });\n\n const change = useCallback(\n (next: Hsva, finished = false) => {\n if (disabled) return;\n const solid = alpha ? next : { ...next, a: 1 };\n const nextText = formatColor(solid, format, alpha);\n setOwn({ text: nextText, color: solid });\n if (nextText !== lastTextRef.current) {\n lastTextRef.current = nextText;\n onChange?.(nextText);\n }\n if (finished) onChangeEnd?.(nextText);\n },\n [disabled, alpha, format, onChange, onChangeEnd]\n );\n const end = useCallback(\n () => onChangeEnd?.(lastTextRef.current),\n [onChangeEnd]\n );\n\n const formatNumber = useNumberFormat();\n const { isCoarsePointer } = useNativeMobilePicker();\n const merged = mergeColorLabels(labels);\n return {\n color,\n text,\n format,\n alpha,\n disabled,\n labels: merged,\n swatches,\n eyeDropper,\n touch: isCoarsePointer,\n percent: writePercent(formatNumber),\n degrees: writeDegrees(formatNumber, merged.degrees),\n change,\n end,\n };\n}\n"],"names":[],"mappings":";;;;;;;;AAAA;AACA;AACA;AAuBA,MAAM,cAAc,GAAgC;AAClD,IAAA,IAAI,EAAE,2BAA2B;AACjC,IAAA,mBAAmB,EAAE,WAAW;AAChC,IAAA,UAAU,EAAE,YAAY;AACxB,IAAA,UAAU,EAAE,YAAY;AACxB,IAAA,GAAG,EAAE,KAAK;AACV,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,OAAO,EAAE,SAAS;AAClB,IAAA,KAAK,EAAE,aAAa;AACpB,IAAA,QAAQ,EAAE,UAAU;AACpB,IAAA,UAAU,EAAE,8BAA8B;CAC3C;AAED;AACO,MAAM,gBAAgB,GAAG,CAC9B,MAAqC,KACN;AAC/B,IAAA,MAAM,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE;AACpC,IAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE;AACvD,QAAA,IAAI,KAAK;AAAE,YAAA,MAAM,CAAC,GAA8B,CAAC,GAAG,KAAK;IAC3D;AACA,IAAA,OAAO,MAAM;AACf;MAEa,KAAK,GAAS,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AAEnD;;;;;;;AAOG;AACI,MAAM,eAAe,GAAG,CAC7B,IAAU,EACV,QAAc,EACd,MAAc,MACJ;AACV,IAAA,GAAG,IAAI;AACP,IAAA,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAC5E,IAAA,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACtC,CAAA;MA0BY,kBAAkB,GAAG,aAAa,CAC7C,IAAI;AAGN;MACa,IAAI,GAAG,MAAK,EAAE;AAE3B;AACA;AACO,MAAM,UAAU,GAA4B;AACjD,IAAA,KAAK,EAAE,KAAK;AACZ,IAAA,IAAI,EAAE,EAAE;AACR,IAAA,MAAM,EAAE,KAAK;AACb,IAAA,KAAK,EAAE,KAAK;AACZ,IAAA,QAAQ,EAAE,IAAI;AACd,IAAA,MAAM,EAAE,cAAc;AACtB,IAAA,QAAQ,EAAE,SAAS;AACnB,IAAA,UAAU,EAAE,KAAK;AACjB,IAAA,KAAK,EAAE,KAAK;AACZ,IAAA,OAAO,EAAE,MAAM;AACf,IAAA,OAAO,EAAE,MAAM;AACf,IAAA,MAAM,EAAE,IAAI;AACZ,IAAA,GAAG,EAAE,IAAI;;AAGX;AACM,SAAU,SAAS,CAAC,IAAY,EAAA;AACpC,IAAA,MAAM,MAAM,GAAG,UAAU,CAAC,kBAAkB,CAAC;AAC7C,IAAA,MAAM,OAAO,GAAG,CAAC,MAAM;IACvB,SAAS,CAAC,MAAK;QACb,IAAI,OAAO,EAAE;AACX,YAAA,QAAQ,CACN,CAAA,YAAA,EAAe,IAAI,UAAU,EAC7B,CAAA,4BAAA,EAA+B,IAAI,CAAA,+BAAA,CAAiC;AAClE,gBAAA,6DAA6D,CAChE;QACH;AACF,IAAA,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACnB,IAAA,OAAO,MAAM;AACf;AAEA;AACA,MAAM,YAAY,GAChB,CAAC,YAAoC,KAAK,CAAC,KAAa,KACtD;AACE,MAAE,YAAY,CAAC,KAAK,GAAG,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;AAChD,MAAE,CAAA,EAAG,KAAK,CAAA,CAAA,CAAG;AAEnB;AACA,MAAM,YAAY,GAChB,CAAC,YAAoC,EAAE,IAAY,KAAK,CAAC,KAAa,KACpE;AACE,MAAE,YAAY,CAAC,KAAK,EAAE;AAClB,QAAA,KAAK,EAAE,MAAM;AACb,QAAA,IAAI,EAAE,QAAQ;AACd,QAAA,WAAW,EAAE,MAAM;KACpB;AACH,MAAE,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,IAAI,EAAE;AAgB1B;;;;AAIG;AACG,SAAU,cAAc,CAAC,EAC7B,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,MAAM,EACN,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,MAAM,GACQ,EAAA;AACd,IAAA,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS;;;IAGxC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAK;QAClC,MAAM,KAAK,GAAG,YAAY,GAAG,KAAK,GAAG,YAAY;AACjD,QAAA,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,EAAE;AACjE,IAAA,CAAC,CAAC;;;;;IAKF,IAAI,YAAY,IAAI,KAAK,KAAK,GAAG,CAAC,IAAI,EAAE;AACtC,QAAA,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC;QAChC,IAAI,MAAM,EAAE;AACV,YAAA,MAAM,CAAC;AACL,gBAAA,IAAI,EAAE,KAAK;gBACX,KAAK,EAAE,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC;AACjD,aAAA,CAAC;QACJ;IACF;IACA,MAAM,KAAK,GAAG,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE;IACxD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC;;;AAI9C,IAAA,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;IAChC,qBAAqB,CAAC,MAAK;AACzB,QAAA,WAAW,CAAC,OAAO,GAAG,IAAI;AAC5B,IAAA,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,IAAU,EAAE,QAAQ,GAAG,KAAK,KAAI;AAC/B,QAAA,IAAI,QAAQ;YAAE;AACd,QAAA,MAAM,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE;QAC9C,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC;QAClD,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACxC,QAAA,IAAI,QAAQ,KAAK,WAAW,CAAC,OAAO,EAAE;AACpC,YAAA,WAAW,CAAC,OAAO,GAAG,QAAQ;AAC9B,YAAA,QAAQ,GAAG,QAAQ,CAAC;QACtB;AACA,QAAA,IAAI,QAAQ;AAAE,YAAA,WAAW,GAAG,QAAQ,CAAC;AACvC,IAAA,CAAC,EACD,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,CACjD;AACD,IAAA,MAAM,GAAG,GAAG,WAAW,CACrB,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,EACxC,CAAC,WAAW,CAAC,CACd;AAED,IAAA,MAAM,YAAY,GAAG,eAAe,EAAE;AACtC,IAAA,MAAM,EAAE,eAAe,EAAE,GAAG,qBAAqB,EAAE;AACnD,IAAA,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC;IACvC,OAAO;QACL,KAAK;QACL,IAAI;QACJ,MAAM;QACN,KAAK;QACL,QAAQ;AACR,QAAA,MAAM,EAAE,MAAM;QACd,QAAQ;QACR,UAAU;AACV,QAAA,KAAK,EAAE,eAAe;AACtB,QAAA,OAAO,EAAE,YAAY,CAAC,YAAY,CAAC;QACnC,OAAO,EAAE,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC;QACnD,MAAM;QACN,GAAG;KACJ;AACH;;"}
@@ -16,11 +16,11 @@ const announceDelay = 100;
16
16
  * again.
17
17
  */
18
18
  const announcementLifetime = 1000;
19
- const noAnnouncements = [];
19
+ const noAnnouncements$1 = [];
20
20
  const nothingWritten = [];
21
21
  const noAnnouncementsYet = {
22
22
  key: 0,
23
- waiting: noAnnouncements,
23
+ waiting: noAnnouncements$1,
24
24
  written: nothingWritten,
25
25
  };
26
26
  // Hides the region with inline styles rather than a class, so it needs no
@@ -122,7 +122,7 @@ function useAnnouncements(items, describe) {
122
122
  setAnnouncements(current => current.key === key
123
123
  ? {
124
124
  ...current,
125
- waiting: noAnnouncements,
125
+ waiting: noAnnouncements$1,
126
126
  written: [
127
127
  ...current.written,
128
128
  ...current.waiting