@neo4j-ndl/react 4.22.0 → 4.23.3

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 (197) hide show
  1. package/lib/cjs/_common/form-control/FormFieldLabel.js +52 -0
  2. package/lib/cjs/_common/form-control/FormFieldLabel.js.map +1 -0
  3. package/lib/cjs/_common/form-control/FormFieldMessage.js +72 -0
  4. package/lib/cjs/_common/form-control/FormFieldMessage.js.map +1 -0
  5. package/lib/cjs/_common/form-control/form-control-types.js +23 -0
  6. package/lib/cjs/_common/form-control/form-control-types.js.map +1 -0
  7. package/lib/cjs/_common/form-control/index.js +39 -0
  8. package/lib/cjs/_common/form-control/index.js.map +1 -0
  9. package/lib/cjs/_common/form-control/use-form-field.js +76 -0
  10. package/lib/cjs/_common/form-control/use-form-field.js.map +1 -0
  11. package/lib/cjs/_common/form-control/use-inline-field-label.js +76 -0
  12. package/lib/cjs/_common/form-control/use-inline-field-label.js.map +1 -0
  13. package/lib/cjs/checkbox/Checkbox.js +9 -21
  14. package/lib/cjs/checkbox/Checkbox.js.map +1 -1
  15. package/lib/cjs/combobox/Combobox.js +75 -53
  16. package/lib/cjs/combobox/Combobox.js.map +1 -1
  17. package/lib/cjs/combobox/combobox-types.js.map +1 -1
  18. package/lib/cjs/date-picker/DatePicker.js.map +1 -1
  19. package/lib/cjs/icons/generated/custom/FloppyDiskArrowTopRight.js +30 -0
  20. package/lib/cjs/icons/generated/custom/FloppyDiskArrowTopRight.js.map +1 -0
  21. package/lib/cjs/icons/generated/custom/OpenQueryTab.js +1 -0
  22. package/lib/cjs/icons/generated/custom/OpenQueryTab.js.map +1 -1
  23. package/lib/cjs/icons/generated/custom/index.js +5 -3
  24. package/lib/cjs/icons/generated/custom/index.js.map +1 -1
  25. package/lib/cjs/next/tree-view/TreeView.js +16 -1
  26. package/lib/cjs/next/tree-view/TreeView.js.map +1 -1
  27. package/lib/cjs/radio/Radio.js +12 -22
  28. package/lib/cjs/radio/Radio.js.map +1 -1
  29. package/lib/cjs/select/Select.js +14 -5
  30. package/lib/cjs/select/Select.js.map +1 -1
  31. package/lib/cjs/select/types.js.map +1 -1
  32. package/lib/cjs/switch/Switch.js +9 -21
  33. package/lib/cjs/switch/Switch.js.map +1 -1
  34. package/lib/cjs/text-area/TextArea.js +8 -21
  35. package/lib/cjs/text-area/TextArea.js.map +1 -1
  36. package/lib/cjs/text-input/TextInput.js +15 -38
  37. package/lib/cjs/text-input/TextInput.js.map +1 -1
  38. package/lib/cjs/text-input/stories/text-input-sizes.story.js +1 -1
  39. package/lib/cjs/text-input/stories/text-input-sizes.story.js.map +1 -1
  40. package/lib/cjs/time-picker/TimePicker.js +13 -7
  41. package/lib/cjs/time-picker/TimePicker.js.map +1 -1
  42. package/lib/cjs/timezone-picker/TimeZonePicker.js +13 -7
  43. package/lib/cjs/timezone-picker/TimeZonePicker.js.map +1 -1
  44. package/lib/esm/_common/form-control/FormFieldLabel.js +45 -0
  45. package/lib/esm/_common/form-control/FormFieldLabel.js.map +1 -0
  46. package/lib/esm/_common/form-control/FormFieldMessage.js +65 -0
  47. package/lib/esm/_common/form-control/FormFieldMessage.js.map +1 -0
  48. package/lib/esm/_common/form-control/form-control-types.js +22 -0
  49. package/lib/esm/_common/form-control/form-control-types.js.map +1 -0
  50. package/lib/esm/_common/form-control/index.js +32 -0
  51. package/lib/esm/_common/form-control/index.js.map +1 -0
  52. package/lib/esm/_common/form-control/use-form-field.js +72 -0
  53. package/lib/esm/_common/form-control/use-form-field.js.map +1 -0
  54. package/lib/esm/_common/form-control/use-inline-field-label.js +69 -0
  55. package/lib/esm/_common/form-control/use-inline-field-label.js.map +1 -0
  56. package/lib/esm/checkbox/Checkbox.js +10 -22
  57. package/lib/esm/checkbox/Checkbox.js.map +1 -1
  58. package/lib/esm/combobox/Combobox.js +76 -54
  59. package/lib/esm/combobox/Combobox.js.map +1 -1
  60. package/lib/esm/combobox/combobox-types.js.map +1 -1
  61. package/lib/esm/date-picker/DatePicker.js.map +1 -1
  62. package/lib/esm/icons/generated/custom/FloppyDiskArrowTopRight.js +28 -0
  63. package/lib/esm/icons/generated/custom/FloppyDiskArrowTopRight.js.map +1 -0
  64. package/lib/esm/icons/generated/custom/OpenQueryTab.js +1 -0
  65. package/lib/esm/icons/generated/custom/OpenQueryTab.js.map +1 -1
  66. package/lib/esm/icons/generated/custom/index.js +1 -0
  67. package/lib/esm/icons/generated/custom/index.js.map +1 -1
  68. package/lib/esm/next/tree-view/TreeView.js +16 -1
  69. package/lib/esm/next/tree-view/TreeView.js.map +1 -1
  70. package/lib/esm/radio/Radio.js +12 -22
  71. package/lib/esm/radio/Radio.js.map +1 -1
  72. package/lib/esm/select/Select.js +14 -5
  73. package/lib/esm/select/Select.js.map +1 -1
  74. package/lib/esm/select/types.js.map +1 -1
  75. package/lib/esm/switch/Switch.js +10 -22
  76. package/lib/esm/switch/Switch.js.map +1 -1
  77. package/lib/esm/text-area/TextArea.js +9 -22
  78. package/lib/esm/text-area/TextArea.js.map +1 -1
  79. package/lib/esm/text-input/TextInput.js +15 -38
  80. package/lib/esm/text-input/TextInput.js.map +1 -1
  81. package/lib/esm/text-input/stories/text-input-sizes.story.js +1 -1
  82. package/lib/esm/text-input/stories/text-input-sizes.story.js.map +1 -1
  83. package/lib/esm/time-picker/TimePicker.js +13 -7
  84. package/lib/esm/time-picker/TimePicker.js.map +1 -1
  85. package/lib/esm/timezone-picker/TimeZonePicker.js +13 -7
  86. package/lib/esm/timezone-picker/TimeZonePicker.js.map +1 -1
  87. package/lib/types/_common/form-control/FormFieldLabel.d.ts +58 -0
  88. package/lib/types/_common/form-control/FormFieldLabel.d.ts.map +1 -0
  89. package/lib/types/_common/form-control/FormFieldMessage.d.ts +46 -0
  90. package/lib/types/_common/form-control/FormFieldMessage.d.ts.map +1 -0
  91. package/lib/types/_common/form-control/form-control-types.d.ts +92 -0
  92. package/lib/types/_common/form-control/form-control-types.d.ts.map +1 -0
  93. package/lib/types/_common/form-control/index.d.ts +33 -0
  94. package/lib/types/_common/form-control/index.d.ts.map +1 -0
  95. package/lib/types/_common/form-control/use-form-field.d.ts +53 -0
  96. package/lib/types/_common/form-control/use-form-field.d.ts.map +1 -0
  97. package/lib/types/_common/form-control/use-inline-field-label.d.ts +69 -0
  98. package/lib/types/_common/form-control/use-inline-field-label.d.ts.map +1 -0
  99. package/lib/types/checkbox/Checkbox.d.ts.map +1 -1
  100. package/lib/types/combobox/Combobox.d.ts.map +1 -1
  101. package/lib/types/combobox/combobox-types.d.ts +6 -5
  102. package/lib/types/combobox/combobox-types.d.ts.map +1 -1
  103. package/lib/types/icons/generated/custom/FloppyDiskArrowTopRight.d.ts +29 -0
  104. package/lib/types/icons/generated/custom/FloppyDiskArrowTopRight.d.ts.map +1 -0
  105. package/lib/types/icons/generated/custom/OpenQueryTab.d.ts +1 -0
  106. package/lib/types/icons/generated/custom/OpenQueryTab.d.ts.map +1 -1
  107. package/lib/types/icons/generated/custom/index.d.ts +1 -0
  108. package/lib/types/icons/generated/custom/index.d.ts.map +1 -1
  109. package/lib/types/next/tree-view/TreeView.d.ts.map +1 -1
  110. package/lib/types/radio/Radio.d.ts.map +1 -1
  111. package/lib/types/select/Select.d.ts.map +1 -1
  112. package/lib/types/select/types.d.ts.map +1 -1
  113. package/lib/types/switch/Switch.d.ts.map +1 -1
  114. package/lib/types/text-area/TextArea.d.ts.map +1 -1
  115. package/lib/types/text-input/TextInput.d.ts.map +1 -1
  116. package/lib/types/text-input/stories/text-input-sizes.story.d.ts.map +1 -1
  117. package/lib/types/time-picker/TimePicker.d.ts +3 -1
  118. package/lib/types/time-picker/TimePicker.d.ts.map +1 -1
  119. package/lib/types/timezone-picker/TimeZonePicker.d.ts +4 -1
  120. package/lib/types/timezone-picker/TimeZonePicker.d.ts.map +1 -1
  121. package/package.json +3 -3
  122. package/skills/ndl-react/SKILL.md +7 -2
  123. package/skills/ndl-react/components/accordion.md +2 -0
  124. package/skills/ndl-react/components/ai/code-preview.md +4 -0
  125. package/skills/ndl-react/components/ai/file-tag.md +2 -0
  126. package/skills/ndl-react/components/ai/image-tag.md +2 -0
  127. package/skills/ndl-react/components/ai/more-files.md +2 -0
  128. package/skills/ndl-react/components/ai/presence.md +2 -0
  129. package/skills/ndl-react/components/ai/preview.md +2 -0
  130. package/skills/ndl-react/components/ai/prompt.md +2 -0
  131. package/skills/ndl-react/components/ai/reasoning.md +2 -0
  132. package/skills/ndl-react/components/ai/response.md +2 -0
  133. package/skills/ndl-react/components/ai/suggestion.md +2 -0
  134. package/skills/ndl-react/components/ai/thinking.md +2 -0
  135. package/skills/ndl-react/components/ai/tool-call.md +2 -0
  136. package/skills/ndl-react/components/ai/user-bubble.md +2 -0
  137. package/skills/ndl-react/components/avatar.md +2 -0
  138. package/skills/ndl-react/components/banner.md +2 -0
  139. package/skills/ndl-react/components/box.md +2 -0
  140. package/skills/ndl-react/components/breadcrumbs.md +2 -0
  141. package/skills/ndl-react/components/checkbox.md +2 -0
  142. package/skills/ndl-react/components/clean-icon-button.md +2 -0
  143. package/skills/ndl-react/components/clipboard-button.md +2 -0
  144. package/skills/ndl-react/components/code-block.md +2 -0
  145. package/skills/ndl-react/components/code.md +2 -0
  146. package/skills/ndl-react/components/color-picker.md +34 -32
  147. package/skills/ndl-react/components/combobox.md +5 -3
  148. package/skills/ndl-react/components/conditional-wrap.md +2 -0
  149. package/skills/ndl-react/components/data-grid.md +2 -0
  150. package/skills/ndl-react/components/date-picker.md +2 -0
  151. package/skills/ndl-react/components/dialog.md +2 -0
  152. package/skills/ndl-react/components/dismissible-tag.md +2 -0
  153. package/skills/ndl-react/components/divider.md +2 -0
  154. package/skills/ndl-react/components/drawer.md +2 -0
  155. package/skills/ndl-react/components/dropdown-button.md +2 -0
  156. package/skills/ndl-react/components/dropzone.md +7 -5
  157. package/skills/ndl-react/components/filled-button.md +2 -0
  158. package/skills/ndl-react/components/flex.md +2 -0
  159. package/skills/ndl-react/components/graph-label.md +2 -0
  160. package/skills/ndl-react/components/icon-button-array.md +2 -0
  161. package/skills/ndl-react/components/icon-button.md +2 -0
  162. package/skills/ndl-react/components/inline-edit.md +2 -0
  163. package/skills/ndl-react/components/kbd.md +2 -0
  164. package/skills/ndl-react/components/loading-bar.md +2 -0
  165. package/skills/ndl-react/components/loading-spinner.md +2 -0
  166. package/skills/ndl-react/components/logo.md +2 -0
  167. package/skills/ndl-react/components/menu.md +2 -0
  168. package/skills/ndl-react/components/modal.md +2 -0
  169. package/skills/ndl-react/components/next/tree-view.md +90 -2
  170. package/skills/ndl-react/components/outlined-button.md +2 -0
  171. package/skills/ndl-react/components/popover.md +2 -0
  172. package/skills/ndl-react/components/progress-bar.md +2 -0
  173. package/skills/ndl-react/components/radio.md +2 -0
  174. package/skills/ndl-react/components/read-only-tag.md +2 -0
  175. package/skills/ndl-react/components/segmented-control.md +2 -0
  176. package/skills/ndl-react/components/select-icon-button.md +2 -0
  177. package/skills/ndl-react/components/select.md +4 -0
  178. package/skills/ndl-react/components/selectable-tag.md +2 -0
  179. package/skills/ndl-react/components/side-navigation.md +2 -0
  180. package/skills/ndl-react/components/skeleton.md +2 -0
  181. package/skills/ndl-react/components/slider.md +2 -0
  182. package/skills/ndl-react/components/spotlight.md +2 -0
  183. package/skills/ndl-react/components/status-indicator.md +2 -0
  184. package/skills/ndl-react/components/status-label.md +2 -0
  185. package/skills/ndl-react/components/switch.md +2 -0
  186. package/skills/ndl-react/components/tabs.md +2 -0
  187. package/skills/ndl-react/components/tag.md +4 -0
  188. package/skills/ndl-react/components/text-area.md +2 -0
  189. package/skills/ndl-react/components/text-button.md +2 -0
  190. package/skills/ndl-react/components/text-input.md +8 -0
  191. package/skills/ndl-react/components/text-link.md +2 -0
  192. package/skills/ndl-react/components/time-picker.md +3 -0
  193. package/skills/ndl-react/components/timezone-picker.md +3 -0
  194. package/skills/ndl-react/components/tooltip.md +2 -0
  195. package/skills/ndl-react/components/tree-view.md +2 -0
  196. package/skills/ndl-react/components/typography.md +2 -0
  197. package/skills/ndl-react/components/wizard.md +2 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-control-types.js","sourceRoot":"","sources":["../../../../src/_common/form-control/form-control-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport type React from 'react';\n\n/** Sizes shared by the form field parts, matching the input sizes. */\ntype FormFieldSize = 'small' | 'medium' | 'large';\n\n/**\n * Severity of the message shown below a form control. An error replaces the\n * help text whenever both are provided.\n *\n * Deliberately a union rather than an error boolean: future severities such\n * as `info` and `warning`, or new ways of presenting a message, only extend\n * this union and the severity presentation map in `FormFieldMessage`.\n */\ntype FormFieldMessageSeverity = 'help' | 'error';\n\n/**\n * The field-related props a form input component maps onto the internal form\n * field. Components pick from these internally so wording, types and behavior\n * stay aligned, while their external prop names stay untouched (for example\n * both TextInput's `showRequiredOrOptionalLabel` and TextArea's `isOptional`\n * map onto `hasRequiredIndicator` here).\n */\ntype FormFieldProps = {\n /** The label displayed above the control */\n label?: React.ReactNode;\n\n /** Assistive text displayed below the control */\n helpText?: React.ReactNode;\n\n /** Error message displayed below the control. When provided, it overrides `helpText` and shows an error icon */\n errorText?: React.ReactNode;\n\n /** Whether the control is required */\n isRequired?: boolean;\n\n /** Whether Required or Optional is shown at the end of the label row */\n hasRequiredIndicator?: boolean;\n\n /** Whether the label is clipped out of the layout, leaving it to screen readers only */\n hasVisuallyHiddenLabel?: boolean;\n};\n\n/**\n * The resolved state of a form field: the ids that tie the label and the\n * message to the control, and which message is currently displayed. Built by\n * `useFormField` and consumed by `FormFieldLabel` and `FormFieldMessage`, so\n * a component never wires ids or picks between help and error itself.\n */\ntype FormFieldState = {\n label?: React.ReactNode;\n /** Id of the label element, for `aria-labelledby` on ARIA widgets. */\n labelId: string;\n /** Id of the control, which an explicit label points at with `htmlFor`. */\n controlId: string;\n /** Id the help text renders with, referenced while it is displayed. */\n helpTextId: string;\n /** Id the error text renders with, referenced while it is displayed. */\n errorTextId: string;\n helpText?: React.ReactNode;\n errorText?: React.ReactNode;\n /** The displayed message. The error text while there is one, else the help text. */\n message?: React.ReactNode;\n /** Id of the displayed message; `undefined` while there is no message. */\n messageId?: string;\n /** Severity of the displayed message; `undefined` while there is no message. */\n messageSeverity?: FormFieldMessageSeverity;\n hasError: boolean;\n isRequired: boolean;\n hasRequiredIndicator: boolean;\n hasVisuallyHiddenLabel: boolean;\n};\n\n// These are DOM attribute names, so the boolean naming convention cannot apply.\n/* eslint-disable @typescript-eslint/naming-convention */\n/**\n * ARIA attributes for the control the field describes. Spread onto the input\n * or the ARIA widget acting as one.\n */\ntype FormFieldControlProps = {\n 'aria-describedby': string | undefined;\n 'aria-invalid': true | undefined;\n};\n/* eslint-enable @typescript-eslint/naming-convention */\n\nexport type {\n FormFieldControlProps,\n FormFieldMessageSeverity,\n FormFieldProps,\n FormFieldSize,\n FormFieldState,\n};\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ *
3
+ * Copyright (c) "Neo4j"
4
+ * Neo4j Sweden AB [http://neo4j.com]
5
+ *
6
+ * This file is part of Neo4j.
7
+ *
8
+ * Neo4j is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ */
21
+ /**
22
+ * The internal form control layer (DES-1772): one implementation of the
23
+ * label, the required indicator, the help text and the error message shared
24
+ * by the form input components, plus the ARIA wiring that ties them to the
25
+ * control. Internal only - not exported from the package index - so a future
26
+ * public field API can be built on top without being constrained by it.
27
+ */
28
+ export { FormFieldLabel } from './FormFieldLabel';
29
+ export { FormFieldMessage } from './FormFieldMessage';
30
+ export { useFormField } from './use-form-field';
31
+ export { useInlineFieldLabel } from './use-inline-field-label';
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/_common/form-control/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAQtD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**\n * The internal form control layer (DES-1772): one implementation of the\n * label, the required indicator, the help text and the error message shared\n * by the form input components, plus the ARIA wiring that ties them to the\n * control. Internal only - not exported from the package index - so a future\n * public field API can be built on top without being constrained by it.\n */\n\nexport { FormFieldLabel } from './FormFieldLabel';\nexport { FormFieldMessage } from './FormFieldMessage';\nexport type {\n FormFieldControlProps,\n FormFieldMessageSeverity,\n FormFieldProps,\n FormFieldSize,\n FormFieldState,\n} from './form-control-types';\nexport { useFormField } from './use-form-field';\nexport { useInlineFieldLabel } from './use-inline-field-label';\n"]}
@@ -0,0 +1,72 @@
1
+ /**
2
+ *
3
+ * Copyright (c) "Neo4j"
4
+ * Neo4j Sweden AB [http://neo4j.com]
5
+ *
6
+ * This file is part of Neo4j.
7
+ *
8
+ * Neo4j is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ */
21
+ import { useId } from 'react';
22
+ /**
23
+ * Resolves the shared form field state: id generation, which of help and
24
+ * error is displayed - an error always replaces the help text - and the ARIA
25
+ * wiring of the control. The parts are rendered by `FormFieldLabel` and
26
+ * `FormFieldMessage`.
27
+ */
28
+ export const useFormField = ({ baseId, controlId, label, helpText, errorText, isRequired = false, hasRequiredIndicator = false, hasVisuallyHiddenLabel = false, extraDescribedByIds = [], }) => {
29
+ const generatedBaseId = useId();
30
+ const base = baseId !== null && baseId !== void 0 ? baseId : generatedBaseId;
31
+ const labelId = `${base}-label`;
32
+ const helpTextId = `${base}-help-text`;
33
+ const errorTextId = `${base}-error-text`;
34
+ const resolvedControlId = controlId !== null && controlId !== void 0 ? controlId : `${base}-control`;
35
+ const hasError = Boolean(errorText);
36
+ const hasHelpText = Boolean(helpText) && !hasError;
37
+ const messageSeverity = hasError
38
+ ? 'error'
39
+ : hasHelpText
40
+ ? 'help'
41
+ : undefined;
42
+ const message = hasError ? errorText : hasHelpText ? helpText : undefined;
43
+ const messageId = hasError
44
+ ? errorTextId
45
+ : hasHelpText
46
+ ? helpTextId
47
+ : undefined;
48
+ const describedByIds = [...extraDescribedByIds, messageId].filter((id) => typeof id === 'string' && id !== '');
49
+ return {
50
+ controlProps: {
51
+ 'aria-describedby': describedByIds.length > 0 ? describedByIds.join(' ') : undefined,
52
+ 'aria-invalid': hasError ? true : undefined,
53
+ },
54
+ field: {
55
+ controlId: resolvedControlId,
56
+ errorText,
57
+ errorTextId,
58
+ hasError,
59
+ hasRequiredIndicator,
60
+ hasVisuallyHiddenLabel,
61
+ helpText,
62
+ helpTextId,
63
+ isRequired,
64
+ label,
65
+ labelId,
66
+ message,
67
+ messageId,
68
+ messageSeverity,
69
+ },
70
+ };
71
+ };
72
+ //# sourceMappingURL=use-form-field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-form-field.js","sourceRoot":"","sources":["../../../../src/_common/form-control/use-form-field.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAkC9B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAC3B,MAAM,EACN,SAAS,EACT,KAAK,EACL,QAAQ,EACR,SAAS,EACT,UAAU,GAAG,KAAK,EAClB,oBAAoB,GAAG,KAAK,EAC5B,sBAAsB,GAAG,KAAK,EAC9B,mBAAmB,GAAG,EAAE,GACJ,EAAsB,EAAE;IAC5C,MAAM,eAAe,GAAG,KAAK,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,eAAe,CAAC;IACvC,MAAM,OAAO,GAAG,GAAG,IAAI,QAAQ,CAAC;IAChC,MAAM,UAAU,GAAG,GAAG,IAAI,YAAY,CAAC;IACvC,MAAM,WAAW,GAAG,GAAG,IAAI,aAAa,CAAC;IACzC,MAAM,iBAAiB,GAAG,SAAS,aAAT,SAAS,cAAT,SAAS,GAAI,GAAG,IAAI,UAAU,CAAC;IAEzD,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;IACnD,MAAM,eAAe,GAAyC,QAAQ;QACpE,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,WAAW;YACX,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1E,MAAM,SAAS,GAAG,QAAQ;QACxB,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,WAAW;YACX,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,SAAS,CAAC;IAEhB,MAAM,cAAc,GAAG,CAAC,GAAG,mBAAmB,EAAE,SAAS,CAAC,CAAC,MAAM,CAC/D,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,EAAE,CAC1D,CAAC;IAEF,OAAO;QACL,YAAY,EAAE;YACZ,kBAAkB,EAChB,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;YAClE,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;SAC5C;QACD,KAAK,EAAE;YACL,SAAS,EAAE,iBAAiB;YAC5B,SAAS;YACT,WAAW;YACX,QAAQ;YACR,oBAAoB;YACpB,sBAAsB;YACtB,QAAQ;YACR,UAAU;YACV,UAAU;YACV,KAAK;YACL,OAAO;YACP,OAAO;YACP,SAAS;YACT,eAAe;SAChB;KACF,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport { useId } from 'react';\n\nimport {\n type FormFieldControlProps,\n type FormFieldMessageSeverity,\n type FormFieldProps,\n type FormFieldState,\n} from './form-control-types';\n\ntype UseFormFieldOptions = FormFieldProps & {\n /**\n * Base for the generated ids, so a component that mints sibling ids of its\n * own (like the Combobox listbox and panel ids) can share one base.\n * Defaults to a generated id.\n */\n baseId?: string;\n /**\n * Overrides the generated control id, for controls that already name their\n * id (the Combobox trigger is `${baseId}-trigger`).\n */\n controlId?: string;\n /**\n * Ids referenced by `aria-describedby` before the message id, skipping\n * falsy entries (such as the TextInput clear/loading hint).\n */\n extraDescribedByIds?: (string | false | undefined)[];\n};\n\ntype UseFormFieldResult = {\n field: FormFieldState;\n /** ARIA attributes to spread onto the control the field describes. */\n controlProps: FormFieldControlProps;\n};\n\n/**\n * Resolves the shared form field state: id generation, which of help and\n * error is displayed - an error always replaces the help text - and the ARIA\n * wiring of the control. The parts are rendered by `FormFieldLabel` and\n * `FormFieldMessage`.\n */\nexport const useFormField = ({\n baseId,\n controlId,\n label,\n helpText,\n errorText,\n isRequired = false,\n hasRequiredIndicator = false,\n hasVisuallyHiddenLabel = false,\n extraDescribedByIds = [],\n}: UseFormFieldOptions): UseFormFieldResult => {\n const generatedBaseId = useId();\n const base = baseId ?? generatedBaseId;\n const labelId = `${base}-label`;\n const helpTextId = `${base}-help-text`;\n const errorTextId = `${base}-error-text`;\n const resolvedControlId = controlId ?? `${base}-control`;\n\n const hasError = Boolean(errorText);\n const hasHelpText = Boolean(helpText) && !hasError;\n const messageSeverity: FormFieldMessageSeverity | undefined = hasError\n ? 'error'\n : hasHelpText\n ? 'help'\n : undefined;\n const message = hasError ? errorText : hasHelpText ? helpText : undefined;\n const messageId = hasError\n ? errorTextId\n : hasHelpText\n ? helpTextId\n : undefined;\n\n const describedByIds = [...extraDescribedByIds, messageId].filter(\n (id): id is string => typeof id === 'string' && id !== '',\n );\n\n return {\n controlProps: {\n 'aria-describedby':\n describedByIds.length > 0 ? describedByIds.join(' ') : undefined,\n 'aria-invalid': hasError ? true : undefined,\n },\n field: {\n controlId: resolvedControlId,\n errorText,\n errorTextId,\n hasError,\n hasRequiredIndicator,\n hasVisuallyHiddenLabel,\n helpText,\n helpTextId,\n isRequired,\n label,\n labelId,\n message,\n messageId,\n messageSeverity,\n },\n };\n};\n"]}
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ *
4
+ * Copyright (c) "Neo4j"
5
+ * Neo4j Sweden AB [http://neo4j.com]
6
+ *
7
+ * This file is part of Neo4j.
8
+ *
9
+ * Neo4j is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ * GNU General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU General Public License
20
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+ import classNames from 'classnames';
23
+ import { useEffect, useMemo } from 'react';
24
+ import { needleWarningMessage } from '../utils';
25
+ /**
26
+ * The inline label of a selection control (Checkbox, Radio, Switch): the
27
+ * control and its label text sit side by side inside one wrapping `<label>`.
28
+ * A string label is wrapped in a titled span so a truncated label stays
29
+ * readable through its tooltip; a custom node is rendered as given, which is
30
+ * also why it needs `ariaLabel` to name the control. Warns when neither a
31
+ * label nor an aria label is provided.
32
+ *
33
+ * Returns the classes of the wrapping label along with its content, so that
34
+ * an adopting component never names a shared class itself. Typography and
35
+ * color sit on that wrapper rather than on the span here, so that a custom
36
+ * node inherits them too.
37
+ *
38
+ * TODO v5: wrap a custom node as well, once the DOM of a custom label can
39
+ * change. This would own all label styling, and the difference between the
40
+ * full-strength inline label and the weaker label above a control could then
41
+ * become a prop here instead of living in two stylesheet classes.
42
+ */
43
+ export const useInlineFieldLabel = ({ componentName, label, ariaLabel, isDisabled = false, isFluid = false, isTruncated = false, }) => {
44
+ useEffect(() => {
45
+ if (Boolean(label) === false && Boolean(ariaLabel) === false) {
46
+ needleWarningMessage(`${componentName} needs a valid label or ariaLabel: https://dequeuniversity.com/rules/axe/4.2/label?application=axeAPI`);
47
+ }
48
+ }, [ariaLabel, componentName, label]);
49
+ const labelContent = useMemo(() => {
50
+ if (Boolean(label) === false) {
51
+ return null;
52
+ }
53
+ if (typeof label === 'string') {
54
+ return (_jsx("span", { title: label, className: classNames('ndl-form-inline-label-text', {
55
+ 'ndl-disabled': isDisabled,
56
+ 'ndl-truncated': isTruncated,
57
+ }), children: label }));
58
+ }
59
+ return label;
60
+ }, [label, isDisabled, isTruncated]);
61
+ return {
62
+ fieldClassName: classNames('ndl-form-inline-field', {
63
+ 'ndl-disabled': isDisabled,
64
+ 'ndl-fluid': isFluid,
65
+ }),
66
+ labelContent,
67
+ };
68
+ };
69
+ //# sourceMappingURL=use-inline-field-label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-inline-field-label.js","sourceRoot":"","sources":["../../../../src/_common/form-control/use-inline-field-label.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAmChD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAClC,aAAa,EACb,KAAK,EACL,SAAS,EACT,UAAU,GAAG,KAAK,EAClB,OAAO,GAAG,KAAK,EACf,WAAW,GAAG,KAAK,GACQ,EAA6B,EAAE;IAC1D,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,OAAO,CAAC,SAAS,CAAC,KAAK,KAAK,EAAE,CAAC;YAC7D,oBAAoB,CAClB,GAAG,aAAa,uGAAuG,CACxH,CAAC;QACJ,CAAC;IACH,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;IAEtC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE;QAChC,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,CACL,eACE,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,UAAU,CAAC,4BAA4B,EAAE;oBAClD,cAAc,EAAE,UAAU;oBAC1B,eAAe,EAAE,WAAW;iBAC7B,CAAC,YAED,KAAK,GACD,CACR,CAAC;QACJ,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IAErC,OAAO;QACL,cAAc,EAAE,UAAU,CAAC,uBAAuB,EAAE;YAClD,cAAc,EAAE,UAAU;YAC1B,WAAW,EAAE,OAAO;SACrB,CAAC;QACF,YAAY;KACb,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport classNames from 'classnames';\nimport { useEffect, useMemo } from 'react';\n\nimport { needleWarningMessage } from '../utils';\n\ntype UseInlineFieldLabelOptions = {\n /** Component name used in the missing-label console warning. */\n componentName: string;\n /** The label rendered next to the control, inside its wrapping `<label>`. */\n label: React.ReactNode;\n // TODO v5: only needed while naming a selection control is split across\n // `label` and `ariaLabel`. Whenever the label alone names the control, as it\n // does on Combobox, the warning below has nothing left to check.\n /** The aria label naming the control when the label is not a string. */\n ariaLabel: string | undefined;\n /** Dims the label and the control it belongs to. */\n isDisabled?: boolean;\n /** Lets the field take the full available width of its container. */\n isFluid?: boolean;\n /**\n * Truncates an overflowing string label to one line with an ellipsis (the\n * `title` set on the span keeps the full text reachable). Radio behavior\n * today; a candidate to unify across all selection controls in v5.\n */\n isTruncated?: boolean;\n};\n\ntype UseInlineFieldLabelResult = {\n /** The label, rendered next to the control. */\n labelContent: React.ReactNode;\n /**\n * Classes for the `<label>` that wraps the control and the label, which the\n * adopting component renders. Owned here so that the shared classes stay an\n * implementation detail of this module.\n */\n fieldClassName: string;\n};\n\n/**\n * The inline label of a selection control (Checkbox, Radio, Switch): the\n * control and its label text sit side by side inside one wrapping `<label>`.\n * A string label is wrapped in a titled span so a truncated label stays\n * readable through its tooltip; a custom node is rendered as given, which is\n * also why it needs `ariaLabel` to name the control. Warns when neither a\n * label nor an aria label is provided.\n *\n * Returns the classes of the wrapping label along with its content, so that\n * an adopting component never names a shared class itself. Typography and\n * color sit on that wrapper rather than on the span here, so that a custom\n * node inherits them too.\n *\n * TODO v5: wrap a custom node as well, once the DOM of a custom label can\n * change. This would own all label styling, and the difference between the\n * full-strength inline label and the weaker label above a control could then\n * become a prop here instead of living in two stylesheet classes.\n */\nexport const useInlineFieldLabel = ({\n componentName,\n label,\n ariaLabel,\n isDisabled = false,\n isFluid = false,\n isTruncated = false,\n}: UseInlineFieldLabelOptions): UseInlineFieldLabelResult => {\n useEffect(() => {\n if (Boolean(label) === false && Boolean(ariaLabel) === false) {\n needleWarningMessage(\n `${componentName} needs a valid label or ariaLabel: https://dequeuniversity.com/rules/axe/4.2/label?application=axeAPI`,\n );\n }\n }, [ariaLabel, componentName, label]);\n\n const labelContent = useMemo(() => {\n if (Boolean(label) === false) {\n return null;\n }\n\n if (typeof label === 'string') {\n return (\n <span\n title={label}\n className={classNames('ndl-form-inline-label-text', {\n 'ndl-disabled': isDisabled,\n 'ndl-truncated': isTruncated,\n })}\n >\n {label}\n </span>\n );\n }\n\n return label;\n }, [label, isDisabled, isTruncated]);\n\n return {\n fieldClassName: classNames('ndl-form-inline-field', {\n 'ndl-disabled': isDisabled,\n 'ndl-fluid': isFluid,\n }),\n labelContent,\n };\n};\n"]}
@@ -32,9 +32,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
32
32
  */
33
33
  import { useMergeRefs } from '@floating-ui/react';
34
34
  import classNames from 'classnames';
35
- import { useEffect, useMemo, useRef } from 'react';
35
+ import { useEffect, useRef } from 'react';
36
+ import { useInlineFieldLabel } from '../_common/form-control';
36
37
  import { useSemiControlledInput } from '../_common/use-semi-controlled-input';
37
- import { needleWarningMessage } from '../_common/utils';
38
38
  export const Checkbox = (_a) => {
39
39
  var _b;
40
40
  var { isChecked, isDefaultChecked, isIndeterminate = false, isFluid = false, isDisabled = false, hasLeadingLabel, ariaLabel, onChange, onClick, value, label, className, style, htmlAttributes, ref } = _a, restProps = __rest(_a, ["isChecked", "isDefaultChecked", "isIndeterminate", "isFluid", "isDisabled", "hasLeadingLabel", "ariaLabel", "onChange", "onClick", "value", "label", "className", "style", "htmlAttributes", "ref"]);
@@ -46,10 +46,6 @@ export const Checkbox = (_a) => {
46
46
  onChange,
47
47
  state: (_b = isChecked !== null && isChecked !== void 0 ? isChecked : isDefaultChecked) !== null && _b !== void 0 ? _b : false,
48
48
  });
49
- const labelWrappingClasses = classNames('ndl-checkbox-label', {
50
- 'ndl-disabled': isDisabled,
51
- 'ndl-fluid': isFluid,
52
- });
53
49
  const checkboxClasses = classNames('ndl-checkbox', className, {
54
50
  'ndl-disabled': isDisabled,
55
51
  });
@@ -58,21 +54,13 @@ export const Checkbox = (_a) => {
58
54
  internalRef.current.indeterminate = isIndeterminate;
59
55
  }
60
56
  }, [isIndeterminate]);
61
- /** TODO: Label handling. We can get rid of this annoying stuff as soon as we do proper form control. */
62
- const labelContent = useMemo(() => {
63
- if (Boolean(label) === false) {
64
- return null;
65
- }
66
- if (typeof label === 'string') {
67
- return (_jsx("span", { title: label, className: "ndl-checkbox-label-text", children: label }));
68
- }
69
- return label;
70
- }, [label]);
71
- useEffect(() => {
72
- if (Boolean(label) === false && Boolean(ariaLabel) === false) {
73
- needleWarningMessage('Checkbox needs a valid label or ariaLabel: https://dequeuniversity.com/rules/axe/4.2/label?application=axeAPI');
74
- }
75
- }, [ariaLabel, label]);
76
- return (_jsxs("label", { className: labelWrappingClasses, children: [hasLeadingLabel === true && labelContent, _jsx("input", Object.assign({ ref: mergedRefs, type: "checkbox", "aria-label": ariaLabel, disabled: isDisabled, checked: isCheckedState, onClick: onClick, onChange: internalOnChange, value: value, style: style, className: checkboxClasses }, restProps, htmlAttributes)), Boolean(hasLeadingLabel) === false && labelContent] }));
57
+ const { labelContent, fieldClassName } = useInlineFieldLabel({
58
+ ariaLabel,
59
+ componentName: 'Checkbox',
60
+ isDisabled,
61
+ isFluid,
62
+ label,
63
+ });
64
+ return (_jsxs("label", { className: fieldClassName, children: [hasLeadingLabel === true && labelContent, _jsx("input", Object.assign({ ref: mergedRefs, type: "checkbox", "aria-label": ariaLabel, disabled: isDisabled, checked: isCheckedState, onClick: onClick, onChange: internalOnChange, value: value, style: style, className: checkboxClasses }, restProps, htmlAttributes)), Boolean(hasLeadingLabel) === false && labelContent] }));
77
65
  };
78
66
  //# sourceMappingURL=Checkbox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../../src/checkbox/Checkbox.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAkCxD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAiBa,EAAE,EAAE;;QAjBjB,EACvB,SAAS,EACT,gBAAgB,EAChB,eAAe,GAAG,KAAK,EACvB,OAAO,GAAG,KAAK,EACf,UAAU,GAAG,KAAK,EAClB,eAAe,EACf,SAAS,EACT,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,SAAS,EACT,KAAK,EACL,cAAc,EACd,GAAG,OAEiC,EADjC,SAAS,cAhBW,qMAiBxB,CADa;IAEZ,MAAM,WAAW,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;IAEpD,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,GAAG,sBAAsB,CAAC;QAChE,SAAS,EAAE,UAAU;QACrB,YAAY,EAAE,SAAS,KAAK,SAAS;QACrC,QAAQ;QACR,KAAK,EAAE,MAAA,SAAS,aAAT,SAAS,cAAT,SAAS,GAAI,gBAAgB,mCAAI,KAAK;KAC9C,CAAC,CAAC;IAEH,MAAM,oBAAoB,GAAG,UAAU,CAAC,oBAAoB,EAAE;QAC5D,cAAc,EAAE,UAAU;QAC1B,WAAW,EAAE,OAAO;KACrB,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,UAAU,CAAC,cAAc,EAAE,SAAS,EAAE;QAC5D,cAAc,EAAE,UAAU;KAC3B,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,WAAW,CAAC,OAAO,CAAC,aAAa,GAAG,eAAe,CAAC;QACtD,CAAC;IACH,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,wGAAwG;IACxG,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE;QAChC,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,CACL,eAAM,KAAK,EAAE,KAAK,EAAE,SAAS,EAAC,yBAAyB,YACpD,KAAK,GACD,CACR,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,OAAO,CAAC,SAAS,CAAC,KAAK,KAAK,EAAE,CAAC;YAC7D,oBAAoB,CAClB,+GAA+G,CAChH,CAAC;QACJ,CAAC;IACH,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;IAEvB,OAAO,CACL,iBAAO,SAAS,EAAE,oBAAoB,aACnC,eAAe,KAAK,IAAI,IAAI,YAAY,EACzC,8BACE,GAAG,EAAE,UAAU,EACf,IAAI,EAAC,UAAU,gBACH,SAAS,EACrB,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,eAAe,IACtB,SAAS,EACT,cAAc,EAClB,EACD,OAAO,CAAC,eAAe,CAAC,KAAK,KAAK,IAAI,YAAY,IAC7C,CACT,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport { useMergeRefs } from '@floating-ui/react';\nimport classNames from 'classnames';\nimport { useEffect, useMemo, useRef } from 'react';\n\nimport { type CommonProps, type HtmlAttributes } from '../_common/types';\nimport { useSemiControlledInput } from '../_common/use-semi-controlled-input';\nimport { needleWarningMessage } from '../_common/utils';\n\ntype CheckboxProps = {\n /** Whether the checkbox is checked */\n isChecked?: boolean;\n /** Checks the input by default in uncontrolled mode */\n isDefaultChecked?: boolean; // isDefaultChecked sounds weird to me?\n /** Whether the checkbox is indeterminate */\n isIndeterminate?: boolean;\n /** Whether the checkbox is disabled */\n isDisabled?: boolean;\n /** Whether the checkbox & label should take the full available width of its container */\n isFluid?: boolean;\n /** The value of the checkbox */\n value?: HtmlAttributes<'input'>['value']; // should this be required?\n /** Whether the label should be displayed before the checkbox */\n hasLeadingLabel?: boolean;\n /** The callback function triggered when the checkbox value changes */\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;\n /** The callback function triggered when the checkbox is clicked */\n onClick?: (event: React.MouseEvent<HTMLInputElement>) => void;\n} & (\n | {\n /** The aria label of the checkbox. Required if the label is not a string. */\n ariaLabel: string;\n /** The label of the checkbox. If not a string, supply an aria label for screen reader support. */\n label?: undefined;\n }\n | {\n ariaLabel?: string;\n label: React.ReactNode;\n }\n);\n\nexport const Checkbox = ({\n isChecked,\n isDefaultChecked,\n isIndeterminate = false,\n isFluid = false,\n isDisabled = false,\n hasLeadingLabel,\n ariaLabel,\n onChange,\n onClick,\n value,\n label,\n className,\n style,\n htmlAttributes,\n ref,\n ...restProps\n}: CommonProps<'input', CheckboxProps>) => {\n const internalRef = useRef<HTMLInputElement>(null);\n const mergedRefs = useMergeRefs([internalRef, ref]);\n\n const [isCheckedState, internalOnChange] = useSemiControlledInput({\n inputType: 'checkbox',\n isControlled: isChecked !== undefined,\n onChange,\n state: isChecked ?? isDefaultChecked ?? false,\n });\n\n const labelWrappingClasses = classNames('ndl-checkbox-label', {\n 'ndl-disabled': isDisabled,\n 'ndl-fluid': isFluid,\n });\n\n const checkboxClasses = classNames('ndl-checkbox', className, {\n 'ndl-disabled': isDisabled,\n });\n\n useEffect(() => {\n if (internalRef.current) {\n internalRef.current.indeterminate = isIndeterminate;\n }\n }, [isIndeterminate]);\n\n /** TODO: Label handling. We can get rid of this annoying stuff as soon as we do proper form control. */\n const labelContent = useMemo(() => {\n if (Boolean(label) === false) {\n return null;\n }\n\n if (typeof label === 'string') {\n return (\n <span title={label} className=\"ndl-checkbox-label-text\">\n {label}\n </span>\n );\n }\n return label;\n }, [label]);\n\n useEffect(() => {\n if (Boolean(label) === false && Boolean(ariaLabel) === false) {\n needleWarningMessage(\n 'Checkbox needs a valid label or ariaLabel: https://dequeuniversity.com/rules/axe/4.2/label?application=axeAPI',\n );\n }\n }, [ariaLabel, label]);\n\n return (\n <label className={labelWrappingClasses}>\n {hasLeadingLabel === true && labelContent}\n <input\n ref={mergedRefs}\n type=\"checkbox\"\n aria-label={ariaLabel}\n disabled={isDisabled}\n checked={isCheckedState}\n onClick={onClick}\n onChange={internalOnChange}\n value={value}\n style={style}\n className={checkboxClasses}\n {...restProps}\n {...htmlAttributes}\n />\n {Boolean(hasLeadingLabel) === false && labelContent}\n </label>\n );\n};\n"]}
1
+ {"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../../src/checkbox/Checkbox.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAuC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAiBa,EAAE,EAAE;;QAjBjB,EACvB,SAAS,EACT,gBAAgB,EAChB,eAAe,GAAG,KAAK,EACvB,OAAO,GAAG,KAAK,EACf,UAAU,GAAG,KAAK,EAClB,eAAe,EACf,SAAS,EACT,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,SAAS,EACT,KAAK,EACL,cAAc,EACd,GAAG,OAEiC,EADjC,SAAS,cAhBW,qMAiBxB,CADa;IAEZ,MAAM,WAAW,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;IAEpD,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,GAAG,sBAAsB,CAAC;QAChE,SAAS,EAAE,UAAU;QACrB,YAAY,EAAE,SAAS,KAAK,SAAS;QACrC,QAAQ;QACR,KAAK,EAAE,MAAA,SAAS,aAAT,SAAS,cAAT,SAAS,GAAI,gBAAgB,mCAAI,KAAK;KAC9C,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,UAAU,CAAC,cAAc,EAAE,SAAS,EAAE;QAC5D,cAAc,EAAE,UAAU;KAC3B,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,WAAW,CAAC,OAAO,CAAC,aAAa,GAAG,eAAe,CAAC;QACtD,CAAC;IACH,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,GAAG,mBAAmB,CAAC;QAC3D,SAAS;QACT,aAAa,EAAE,UAAU;QACzB,UAAU;QACV,OAAO;QACP,KAAK;KACN,CAAC,CAAC;IAEH,OAAO,CACL,iBAAO,SAAS,EAAE,cAAc,aAC7B,eAAe,KAAK,IAAI,IAAI,YAAY,EACzC,8BACE,GAAG,EAAE,UAAU,EACf,IAAI,EAAC,UAAU,gBACH,SAAS,EACrB,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,eAAe,IACtB,SAAS,EACT,cAAc,EAClB,EACD,OAAO,CAAC,eAAe,CAAC,KAAK,KAAK,IAAI,YAAY,IAC7C,CACT,CAAC;AACJ,CAAC,CAAC","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport { useMergeRefs } from '@floating-ui/react';\nimport classNames from 'classnames';\nimport { useEffect, useRef } from 'react';\n\nimport { useInlineFieldLabel } from '../_common/form-control';\nimport { type CommonProps, type HtmlAttributes } from '../_common/types';\nimport { useSemiControlledInput } from '../_common/use-semi-controlled-input';\n\ntype CheckboxProps = {\n /** Whether the checkbox is checked */\n isChecked?: boolean;\n /** Checks the input by default in uncontrolled mode */\n isDefaultChecked?: boolean; // isDefaultChecked sounds weird to me?\n /** Whether the checkbox is indeterminate */\n isIndeterminate?: boolean;\n /** Whether the checkbox is disabled */\n isDisabled?: boolean;\n /** Whether the checkbox & label should take the full available width of its container */\n isFluid?: boolean;\n /** The value of the checkbox */\n value?: HtmlAttributes<'input'>['value']; // should this be required?\n /** Whether the label should be displayed before the checkbox */\n hasLeadingLabel?: boolean;\n /** The callback function triggered when the checkbox value changes */\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;\n /** The callback function triggered when the checkbox is clicked */\n onClick?: (event: React.MouseEvent<HTMLInputElement>) => void;\n} & (\n | {\n /** The aria label of the checkbox. Required if the label is not a string. */\n ariaLabel: string;\n // TODO v5: inconsistent naming. Naming the checkbox takes a union of\n // `label` and `ariaLabel`, so the accessible name comes from a different\n // prop depending on what the label renders. Combobox instead requires the\n // label and clips it with `hasVisuallyHiddenLabel`, and TextInput reaches\n // for htmlAttributes['aria-label'].\n /** The label of the checkbox. If not a string, supply an aria label for screen reader support. */\n label?: undefined;\n }\n | {\n ariaLabel?: string;\n label: React.ReactNode;\n }\n);\n\nexport const Checkbox = ({\n isChecked,\n isDefaultChecked,\n isIndeterminate = false,\n isFluid = false,\n isDisabled = false,\n hasLeadingLabel,\n ariaLabel,\n onChange,\n onClick,\n value,\n label,\n className,\n style,\n htmlAttributes,\n ref,\n ...restProps\n}: CommonProps<'input', CheckboxProps>) => {\n const internalRef = useRef<HTMLInputElement>(null);\n const mergedRefs = useMergeRefs([internalRef, ref]);\n\n const [isCheckedState, internalOnChange] = useSemiControlledInput({\n inputType: 'checkbox',\n isControlled: isChecked !== undefined,\n onChange,\n state: isChecked ?? isDefaultChecked ?? false,\n });\n\n const checkboxClasses = classNames('ndl-checkbox', className, {\n 'ndl-disabled': isDisabled,\n });\n\n useEffect(() => {\n if (internalRef.current) {\n internalRef.current.indeterminate = isIndeterminate;\n }\n }, [isIndeterminate]);\n\n const { labelContent, fieldClassName } = useInlineFieldLabel({\n ariaLabel,\n componentName: 'Checkbox',\n isDisabled,\n isFluid,\n label,\n });\n\n return (\n <label className={fieldClassName}>\n {hasLeadingLabel === true && labelContent}\n <input\n ref={mergedRefs}\n type=\"checkbox\"\n aria-label={ariaLabel}\n disabled={isDisabled}\n checked={isCheckedState}\n onClick={onClick}\n onChange={internalOnChange}\n value={value}\n style={style}\n className={checkboxClasses}\n {...restProps}\n {...htmlAttributes}\n />\n {Boolean(hasLeadingLabel) === false && labelContent}\n </label>\n );\n};\n"]}
@@ -34,13 +34,14 @@ import { FloatingFocusManager, FloatingPortal, useMergeRefs, } from '@floating-u
34
34
  import { useVirtualizer } from '@tanstack/react-virtual';
35
35
  import classNames from 'classnames';
36
36
  import { Fragment, useEffect, useId, useMemo, useRef, useState } from 'react';
37
+ import { FormFieldLabel, FormFieldMessage, useFormField, } from '../_common/form-control';
37
38
  import { useSemicontrolledState } from '../_common/use-semi-controlled-state';
38
39
  import { needleWarningMessage } from '../_common/utils';
39
40
  import { Checkbox } from '../checkbox';
40
41
  import { ConditionalWrap } from '../conditional-wrap';
41
42
  import { DismissibleTag } from '../dismissible-tag';
42
43
  import { Divider } from '../divider';
43
- import { CheckIconOutline, ChevronDownIconOutline, ExclamationCircleIconSolid, MagnifyingGlassIconOutline, PlusIconOutline, XMarkIconOutline, } from '../icons/generated/heroIcons';
44
+ import { CheckIconOutline, ChevronDownIconOutline, MagnifyingGlassIconOutline, PlusIconOutline, XMarkIconOutline, } from '../icons/generated/heroIcons';
44
45
  import { LoadingSpinner } from '../loading-spinner';
45
46
  import { ReadOnlyTag } from '../read-only-tag';
46
47
  import { useNeedleTheme } from '../theme';
@@ -102,9 +103,6 @@ export const Combobox = ({ label, name, formId, autoComplete, triggerRef, hasVis
102
103
  const listboxId = `${baseId}-listbox`;
103
104
  const panelId = `${baseId}-panel`;
104
105
  const triggerId = `${baseId}-trigger`;
105
- const labelId = `${baseId}-label`;
106
- const helpTextId = `${baseId}-help-text`;
107
- const errorTextId = `${baseId}-error-text`;
108
106
  const initialValue = getInitialValue(value, defaultValue, isMulti);
109
107
  const [selectedValue, setSelectedValue, resetSelectedValue] = useSemicontrolledState({
110
108
  isControlled: value !== undefined,
@@ -138,7 +136,6 @@ export const Combobox = ({ label, name, formId, autoComplete, triggerRef, hasVis
138
136
  isPortaled,
139
137
  onOpenChange: setIsPanelOpen,
140
138
  });
141
- const hasCustomTrigger = renderTrigger !== undefined;
142
139
  const builtInTriggerRef = useMergeRefs([internalTriggerRef, triggerRef]);
143
140
  const customTriggerRef = useMergeRefs([
144
141
  refs.setReference,
@@ -462,13 +459,22 @@ export const Combobox = ({ label, name, formId, autoComplete, triggerRef, hasVis
462
459
  ? actionRows.map((row) => getOptionId(row)).join(' ')
463
460
  : undefined;
464
461
  const isDialogPanel = isSearchable;
465
- // An error replaces the help text, so only one message is ever rendered and
466
- // only that one is described.
467
- const displayedErrorText = errorText !== null && errorText !== void 0 ? errorText : nativeValidationMessage;
468
- const hasError = Boolean(displayedErrorText);
469
- const hasHelpText = Boolean(helpText) && !hasError;
470
- const messageId = hasError ? errorTextId : helpTextId;
471
- const describedById = hasError || hasHelpText ? messageId : undefined;
462
+ // The shared form field resolves the ids and which message is displayed -
463
+ // an error always replaces the help text. A native validation failure
464
+ // reported by the hidden form control acts as the error while no errorText
465
+ // is set.
466
+ const { field: formField, controlProps: fieldControlProps } = useFormField({
467
+ baseId,
468
+ controlId: triggerId,
469
+ errorText: errorText !== null && errorText !== void 0 ? errorText : nativeValidationMessage,
470
+ hasRequiredIndicator,
471
+ hasVisuallyHiddenLabel,
472
+ helpText,
473
+ isRequired,
474
+ label,
475
+ });
476
+ const hasError = formField.hasError;
477
+ const labelId = formField.labelId;
472
478
  const triggerAria = {
473
479
  'aria-activedescendant': isPanelOpen && !isSearchable ? activeOptionId : undefined,
474
480
  'aria-controls': getTriggerControlsId({
@@ -478,11 +484,11 @@ export const Combobox = ({ label, name, formId, autoComplete, triggerRef, hasVis
478
484
  listboxId,
479
485
  panelId,
480
486
  }),
481
- 'aria-describedby': describedById,
487
+ 'aria-describedby': fieldControlProps['aria-describedby'],
482
488
  'aria-disabled': isDisabled ? true : undefined,
483
489
  'aria-expanded': isPanelOpen,
484
490
  'aria-haspopup': isDialogPanel ? 'dialog' : 'listbox',
485
- 'aria-invalid': hasError ? true : undefined,
491
+ 'aria-invalid': fieldControlProps['aria-invalid'],
486
492
  'aria-required': isRequired ? true : undefined,
487
493
  // The label points at the trigger through `htmlFor`, so clicking it moves
488
494
  // focus to the trigger.
@@ -502,13 +508,13 @@ export const Combobox = ({ label, name, formId, autoComplete, triggerRef, hasVis
502
508
  // is referenced, keeping "Optional" out of the accessible name.
503
509
  'aria-labelledby': labelId, ref: builtInTriggerRef });
504
510
  const field = {
505
- errorText: displayedErrorText,
506
- errorTextId,
511
+ errorText: formField.errorText,
512
+ errorTextId: formField.errorTextId,
507
513
  hasError,
508
514
  hasRequiredIndicator,
509
515
  hasVisuallyHiddenLabel,
510
516
  helpText,
511
- helpTextId,
517
+ helpTextId: formField.helpTextId,
512
518
  isRequired,
513
519
  label,
514
520
  labelId,
@@ -563,20 +569,27 @@ export const Combobox = ({ label, name, formId, autoComplete, triggerRef, hasVis
563
569
  id: listboxId,
564
570
  role: 'listbox',
565
571
  }), { className: "ndl-combobox-actions", children: [_jsx(Divider, { className: "ndl-combobox-action-divider", htmlAttributes: { 'aria-hidden': true } }), actionRows.map((row, index) => (_jsxs(Fragment, { children: [index > 0 && getAction(row).hasDivider === true && (_jsx(Divider, { className: "ndl-combobox-action-divider", htmlAttributes: { 'aria-hidden': true } })), renderAction(row)] }, row.value)))] })))] })));
566
- return (_jsxs("div", Object.assign({ ref: ref, style: style, className: classNames('ndl-combobox', {
572
+ const formControl = name !== undefined && (_jsx(ComboboxFormControl, { ref: internalTriggerRef, name: name, form: formId, autoComplete: autoComplete, options: formOptions, selectedOptions: selectedOptions, resetSelectedOptions: resetSelectedOptions, isMulti: isMulti, isDisabled: isDisabled, isRequired: isRequired, isSelectionControlled: value !== undefined, onValueChange: setSelectedValue, onInvalid: setNativeValidationMessage, onReset: resetFromForm }));
573
+ const floatingPanel = context.open && (_jsx(ConditionalWrap, { shouldWrap: isPanelPortaled, wrap: (wrapChildren) => (_jsx(FloatingPortal, { root: portalTarget, children: wrapChildren })), children: _jsx(FloatingFocusManager, { context: context, modal: false,
574
+ // A select-only combobox keeps DOM focus on the trigger and speaks
575
+ // through aria-activedescendant, so nothing inside is focused.
576
+ initialFocus: isSearchable ? searchInputRef : -1, returnFocus: internalTriggerRef, children: panel }) }));
577
+ // No root element around a custom trigger, so the trigger is a real child of
578
+ // whatever holds the combobox and sibling-based selectors there match it.
579
+ // The form control goes after the trigger to keep it out of leading
580
+ // positional slots.
581
+ if (renderTrigger !== undefined) {
582
+ return (_jsxs(_Fragment, { children: [renderTrigger(triggerProps, triggerState), formControl, floatingPanel] }));
583
+ }
584
+ return (_jsxs("div", Object.assign({ ref: ref, style: style, className: classNames('ndl-combobox ndl-combobox-field', {
567
585
  'ndl-clean': isClean,
568
- 'ndl-combobox-custom-trigger': hasCustomTrigger,
569
- 'ndl-combobox-field': !hasCustomTrigger,
570
586
  'ndl-disabled': isDisabled,
571
587
  'ndl-fluid': isFluid,
572
588
  'ndl-has-error': hasError,
573
589
  'ndl-large': size === 'large',
574
590
  'ndl-medium': size === 'medium',
575
591
  'ndl-small': size === 'small',
576
- }, className) }, htmlAttributes, { children: [name !== undefined && (_jsx(ComboboxFormControl, { ref: internalTriggerRef, name: name, form: formId, autoComplete: autoComplete, options: formOptions, selectedOptions: selectedOptions, resetSelectedOptions: resetSelectedOptions, isMulti: isMulti, isDisabled: isDisabled, isRequired: isRequired, isSelectionControlled: value !== undefined, onValueChange: setSelectedValue, onInvalid: setNativeValidationMessage, onReset: resetFromForm })), renderTrigger === undefined ? (_jsx(Control, { triggerProps: builtInTriggerProps, state: triggerState, field: field, placeholder: placeholder, isClearable: isClearable, isClean: isClean, size: size, maxRows: isMulti ? maxRows : undefined, components: components, referenceProps: getReferenceProps(), referenceRef: refs.setReference })) : (renderTrigger(triggerProps, triggerState)), context.open && (_jsx(ConditionalWrap, { shouldWrap: isPanelPortaled, wrap: (wrapChildren) => (_jsx(FloatingPortal, { root: portalTarget, children: wrapChildren })), children: _jsx(FloatingFocusManager, { context: context, modal: false,
577
- // A select-only combobox keeps DOM focus on the trigger and speaks
578
- // through aria-activedescendant, so nothing inside is focused.
579
- initialFocus: isSearchable ? searchInputRef : -1, returnFocus: internalTriggerRef, children: panel }) }))] })));
592
+ }, className) }, htmlAttributes, { children: [formControl, _jsx(Control, { triggerProps: builtInTriggerProps, state: triggerState, field: field, placeholder: placeholder, isClearable: isClearable, isClean: isClean, size: size, maxRows: isMulti ? maxRows : undefined, components: components, referenceProps: getReferenceProps(), referenceRef: refs.setReference }), floatingPanel] })));
580
593
  };
581
594
  const getTriggerControlsId = ({ isPanelOpen, isDialogPanel, hasRows, panelId, listboxId, }) => {
582
595
  if (!isPanelOpen) {
@@ -595,6 +608,42 @@ const getSelectedOptions = (value) => {
595
608
  }
596
609
  return value === null ? [] : [value];
597
610
  };
611
+ /**
612
+ * The public `ComboboxFieldState` keeps its own shape - a custom trigger
613
+ * renders from it - so the built-in field parts map it onto the shared form
614
+ * field state (`_common/form-control`) instead of taking it directly.
615
+ */
616
+ const toFormFieldState = (field) => {
617
+ const hasHelpText = Boolean(field.helpText) && !field.hasError;
618
+ return {
619
+ controlId: field.triggerId,
620
+ errorText: field.errorText,
621
+ errorTextId: field.errorTextId,
622
+ hasError: field.hasError,
623
+ hasRequiredIndicator: field.hasRequiredIndicator,
624
+ hasVisuallyHiddenLabel: field.hasVisuallyHiddenLabel,
625
+ helpText: field.helpText,
626
+ helpTextId: field.helpTextId,
627
+ isRequired: field.isRequired,
628
+ label: field.label,
629
+ labelId: field.labelId,
630
+ message: field.hasError
631
+ ? field.errorText
632
+ : hasHelpText
633
+ ? field.helpText
634
+ : undefined,
635
+ messageId: field.hasError
636
+ ? field.errorTextId
637
+ : hasHelpText
638
+ ? field.helpTextId
639
+ : undefined,
640
+ messageSeverity: field.hasError
641
+ ? 'error'
642
+ : hasHelpText
643
+ ? 'help'
644
+ : undefined,
645
+ };
646
+ };
598
647
  /**
599
648
  * A native select mirrors the custom control for submission, autofill,
600
649
  * constraint validation, and reset. It is hidden from assistive technology
@@ -691,6 +740,7 @@ const Control = ({ triggerProps, state, field, placeholder, isClearable, isClean
691
740
  const { isOpen, isMulti, isDisabled, selectedOptions, clear, remove } = state;
692
741
  const { MultiValue, SingleValue } = components;
693
742
  const { ref: triggerRef } = triggerProps, triggerAria = __rest(triggerProps, ["ref"]);
743
+ const sharedField = toFormFieldState(field);
694
744
  const hasValue = selectedOptions.length > 0;
695
745
  const singleValueOption = isMulti ? undefined : selectedOptions[0];
696
746
  // Selected values are shown as tags, so the trigger renders nothing and is
@@ -800,7 +850,7 @@ const Control = ({ triggerProps, state, field, placeholder, isClearable, isClean
800
850
  return (
801
851
  // A fragment, so the label, the control, and the message are rows of the
802
852
  // combobox itself. A custom trigger replaces all three.
803
- _jsxs(_Fragment, { children: [_jsx(FieldLabel, { field: field, size: size, onLabelClick: handleLabelClick }), _jsxs("div", Object.assign({ ref: referenceRef, className: classNames('ndl-combobox-control', {
853
+ _jsxs(_Fragment, { children: [_jsx(FormFieldLabel, { field: sharedField, size: size, onLabelClick: handleLabelClick }), _jsxs("div", Object.assign({ ref: referenceRef, className: classNames('ndl-combobox-control', {
804
854
  'ndl-clean': isClean,
805
855
  'ndl-disabled': isDisabled,
806
856
  'ndl-has-error': field.hasError,
@@ -846,35 +896,7 @@ const Control = ({ triggerProps, state, field, placeholder, isClearable, isClean
846
896
  // Select.
847
897
  _jsxs("div", { className: "ndl-combobox-clear", children: [_jsx("button", { type: "button", className: "ndl-combobox-clear-button", "aria-label": "Clear selection", onKeyDown: handleAuxiliaryActionKeyDown, onClick: handleClear, children: _jsx(XMarkIconOutline, {}) }), _jsx("div", { className: "ndl-combobox-divider" })] })), _jsx("div", { className: classNames('ndl-combobox-dropdown-indicator', {
848
898
  'ndl-open': isOpen,
849
- }), children: _jsx(ChevronDownIconOutline, { "aria-hidden": "true", className: "ndl-combobox-icon" }) })] })] })), _jsx(FieldMessage, { field: field, size: size })] }));
850
- };
851
- /**
852
- * The trigger is a button with `role="combobox"`, which takes its accessible
853
- * name from the author rather than from its content, so the label is referenced
854
- * through `aria-labelledby` instead of wrapping the control in a `<label>`.
855
- * The label is still a `<label>` associated through `htmlFor`, and clicking it
856
- * moves focus to the trigger, as with a native select. It deliberately does
857
- * not open the panel, which is also what a native select label leaves alone.
858
- */
859
- const FieldLabel = ({ field, size, onLabelClick }) => {
860
- const { label, labelId, triggerId, hasVisuallyHiddenLabel, hasRequiredIndicator, isRequired, } = field;
861
- const variant = size === 'large' ? 'body-large' : 'body-medium';
862
- return (_jsxs("div", { className: classNames('ndl-combobox-label-row', {
863
- 'ndl-visually-hidden': hasVisuallyHiddenLabel,
864
- }), children: [_jsx(Typography, { as: "label", variant: variant, className: "ndl-combobox-label", htmlAttributes: {
865
- htmlFor: triggerId,
866
- id: labelId,
867
- onClick: onLabelClick,
868
- }, children: label }), hasRequiredIndicator && (_jsx(Typography, { variant: variant, className: "ndl-combobox-label-indicator", children: isRequired ? 'Required' : 'Optional' }))] }));
869
- };
870
- /**
871
- * Rendered whether or not there is a message, so the live region is in the
872
- * document before the text arrives and a validation error is announced.
873
- */
874
- const FieldMessage = ({ field, size }) => {
875
- const { errorText, hasError, helpText, helpTextId, errorTextId } = field;
876
- const variant = size === 'large' ? 'body-medium' : 'body-small';
877
- return (_jsxs("div", { className: "ndl-combobox-message", "aria-live": "polite", children: [hasError && (_jsxs(_Fragment, { children: [_jsx("div", { className: "ndl-error-icon", children: _jsx(ExclamationCircleIconSolid, {}) }), _jsx(Typography, { variant: variant, className: "ndl-error-text", htmlAttributes: { id: errorTextId }, children: errorText })] })), !hasError && Boolean(helpText) && (_jsx(Typography, { variant: variant, htmlAttributes: { id: helpTextId }, children: helpText }))] }));
899
+ }), children: _jsx(ChevronDownIconOutline, { "aria-hidden": "true", className: "ndl-combobox-icon" }) })] })] })), _jsx(FormFieldMessage, { field: sharedField, size: size })] }));
878
900
  };
879
901
  const Panel = (_a) => {
880
902
  var { children, ref, id, ariaLabel, isDialog, className, style } = _a, restProps = __rest(_a, ["children", "ref", "id", "ariaLabel", "isDialog", "className", "style"]);