@aglyn/shared-ui-jsx-forms 1.0.0-beta.143

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 (150) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +52 -0
  4. package/src/index.d.ts +17 -0
  5. package/src/index.js +18 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/color-picker-tokens.d.ts +135 -0
  8. package/src/lib/components/color-picker-tokens.js +431 -0
  9. package/src/lib/components/color-picker-tokens.js.map +1 -0
  10. package/src/lib/components/color-picker.component.d.ts +64 -0
  11. package/src/lib/components/color-picker.component.js +322 -0
  12. package/src/lib/components/color-picker.component.js.map +1 -0
  13. package/src/lib/components/create-artifact-drawer.component.d.ts +100 -0
  14. package/src/lib/components/create-artifact-drawer.component.js +184 -0
  15. package/src/lib/components/create-artifact-drawer.component.js.map +1 -0
  16. package/src/lib/components/grid-form-template.component.d.ts +21 -0
  17. package/src/lib/components/grid-form-template.component.js +81 -0
  18. package/src/lib/components/grid-form-template.component.js.map +1 -0
  19. package/src/lib/components/icon-select.component.d.ts +52 -0
  20. package/src/lib/components/icon-select.component.js +453 -0
  21. package/src/lib/components/icon-select.component.js.map +1 -0
  22. package/src/lib/components/select.component.d.ts +58 -0
  23. package/src/lib/components/select.component.js +107 -0
  24. package/src/lib/components/select.component.js.map +1 -0
  25. package/src/lib/components/switch.component.d.ts +38 -0
  26. package/src/lib/components/switch.component.js +76 -0
  27. package/src/lib/components/switch.component.js.map +1 -0
  28. package/src/lib/components/text-field.component.d.ts +45 -0
  29. package/src/lib/components/text-field.component.js +77 -0
  30. package/src/lib/components/text-field.component.js.map +1 -0
  31. package/src/lib/components/textarea.component.d.ts +45 -0
  32. package/src/lib/components/textarea.component.js +61 -0
  33. package/src/lib/components/textarea.component.js.map +1 -0
  34. package/src/lib/components/toggle-button.component.d.ts +27 -0
  35. package/src/lib/components/toggle-button.component.js +116 -0
  36. package/src/lib/components/toggle-button.component.js.map +1 -0
  37. package/src/lib/constants/component-mappers.d.ts +42 -0
  38. package/src/lib/constants/component-mappers.js +70 -0
  39. package/src/lib/constants/component-mappers.js.map +1 -0
  40. package/src/lib/constants/dynamic-fields.d.ts +45 -0
  41. package/src/lib/constants/dynamic-fields.js +65 -0
  42. package/src/lib/constants/dynamic-fields.js.map +1 -0
  43. package/src/lib/constants/field-configurations.d.ts +53 -0
  44. package/src/lib/constants/field-configurations.js +121 -0
  45. package/src/lib/constants/field-configurations.js.map +1 -0
  46. package/src/lib/constants/flags.d.ts +72 -0
  47. package/src/lib/constants/flags.js +69 -0
  48. package/src/lib/constants/flags.js.map +1 -0
  49. package/src/lib/hocs/with-grid-item.d.ts +25 -0
  50. package/src/lib/hocs/with-grid-item.js +48 -0
  51. package/src/lib/hocs/with-grid-item.js.map +1 -0
  52. package/src/lib/jsx-forms.d.ts +41 -0
  53. package/src/lib/jsx-forms.js +42 -0
  54. package/src/lib/jsx-forms.js.map +1 -0
  55. package/src/lib/mapper/breakpoint-span.d.ts +73 -0
  56. package/src/lib/mapper/breakpoint-span.js +346 -0
  57. package/src/lib/mapper/breakpoint-span.js.map +1 -0
  58. package/src/lib/mapper/checkbox.d.ts +43 -0
  59. package/src/lib/mapper/checkbox.js +111 -0
  60. package/src/lib/mapper/checkbox.js.map +1 -0
  61. package/src/lib/mapper/css-border.d.ts +113 -0
  62. package/src/lib/mapper/css-border.js +410 -0
  63. package/src/lib/mapper/css-border.js.map +1 -0
  64. package/src/lib/mapper/css-dimension.d.ts +89 -0
  65. package/src/lib/mapper/css-dimension.js +327 -0
  66. package/src/lib/mapper/css-dimension.js.map +1 -0
  67. package/src/lib/mapper/css-gradient.d.ts +110 -0
  68. package/src/lib/mapper/css-gradient.js +720 -0
  69. package/src/lib/mapper/css-gradient.js.map +1 -0
  70. package/src/lib/mapper/data-table.d.ts +23 -0
  71. package/src/lib/mapper/data-table.js +296 -0
  72. package/src/lib/mapper/data-table.js.map +1 -0
  73. package/src/lib/mapper/date-picker.d.ts +26 -0
  74. package/src/lib/mapper/date-picker.js +69 -0
  75. package/src/lib/mapper/date-picker.js.map +1 -0
  76. package/src/lib/mapper/dual-list-select.d.ts +115 -0
  77. package/src/lib/mapper/dual-list-select.js +353 -0
  78. package/src/lib/mapper/dual-list-select.js.map +1 -0
  79. package/src/lib/mapper/field-array.d.ts +71 -0
  80. package/src/lib/mapper/field-array.js +326 -0
  81. package/src/lib/mapper/field-array.js.map +1 -0
  82. package/src/lib/mapper/form-field-grid.d.ts +155 -0
  83. package/src/lib/mapper/form-field-grid.js +194 -0
  84. package/src/lib/mapper/form-field-grid.js.map +1 -0
  85. package/src/lib/mapper/index.d.ts +50 -0
  86. package/src/lib/mapper/index.js +49 -0
  87. package/src/lib/mapper/index.js.map +1 -0
  88. package/src/lib/mapper/multiple-choice-list.d.ts +32 -0
  89. package/src/lib/mapper/multiple-choice-list.js +105 -0
  90. package/src/lib/mapper/multiple-choice-list.js.map +1 -0
  91. package/src/lib/mapper/plain-text.d.ts +31 -0
  92. package/src/lib/mapper/plain-text.js +48 -0
  93. package/src/lib/mapper/plain-text.js.map +1 -0
  94. package/src/lib/mapper/preset-choice.d.ts +106 -0
  95. package/src/lib/mapper/preset-choice.js +267 -0
  96. package/src/lib/mapper/preset-choice.js.map +1 -0
  97. package/src/lib/mapper/radio.d.ts +40 -0
  98. package/src/lib/mapper/radio.js +122 -0
  99. package/src/lib/mapper/radio.js.map +1 -0
  100. package/src/lib/mapper/select.d.ts +60 -0
  101. package/src/lib/mapper/select.js +203 -0
  102. package/src/lib/mapper/select.js.map +1 -0
  103. package/src/lib/mapper/slider.d.ts +45 -0
  104. package/src/lib/mapper/slider.js +119 -0
  105. package/src/lib/mapper/slider.js.map +1 -0
  106. package/src/lib/mapper/stored-field-value.d.ts +23 -0
  107. package/src/lib/mapper/stored-field-value.js +29 -0
  108. package/src/lib/mapper/stored-field-value.js.map +1 -0
  109. package/src/lib/mapper/sub-form.d.ts +39 -0
  110. package/src/lib/mapper/sub-form.js +108 -0
  111. package/src/lib/mapper/sub-form.js.map +1 -0
  112. package/src/lib/mapper/switch.d.ts +47 -0
  113. package/src/lib/mapper/switch.js +93 -0
  114. package/src/lib/mapper/switch.js.map +1 -0
  115. package/src/lib/mapper/tabs.d.ts +34 -0
  116. package/src/lib/mapper/tabs.js +64 -0
  117. package/src/lib/mapper/tabs.js.map +1 -0
  118. package/src/lib/mapper/text-field.d.ts +33 -0
  119. package/src/lib/mapper/text-field.js +88 -0
  120. package/src/lib/mapper/text-field.js.map +1 -0
  121. package/src/lib/mapper/textarea.d.ts +31 -0
  122. package/src/lib/mapper/textarea.js +68 -0
  123. package/src/lib/mapper/textarea.js.map +1 -0
  124. package/src/lib/mapper/theme-scale.d.ts +79 -0
  125. package/src/lib/mapper/theme-scale.js +162 -0
  126. package/src/lib/mapper/theme-scale.js.map +1 -0
  127. package/src/lib/mapper/time-picker.d.ts +26 -0
  128. package/src/lib/mapper/time-picker.js +69 -0
  129. package/src/lib/mapper/time-picker.js.map +1 -0
  130. package/src/lib/mapper/types.d.ts +43 -0
  131. package/src/lib/mapper/types.js +22 -0
  132. package/src/lib/mapper/types.js.map +1 -0
  133. package/src/lib/mapper/validation-error.d.ts +24 -0
  134. package/src/lib/mapper/validation-error.js +24 -0
  135. package/src/lib/mapper/validation-error.js.map +1 -0
  136. package/src/lib/mapper/wizard.d.ts +21 -0
  137. package/src/lib/mapper/wizard.js +203 -0
  138. package/src/lib/mapper/wizard.js.map +1 -0
  139. package/src/lib/types.d.ts +19 -0
  140. package/src/lib/types.js +18 -0
  141. package/src/lib/types.js.map +1 -0
  142. package/src/lib/utils/option-is-equal-to-value.d.ts +20 -0
  143. package/src/lib/utils/option-is-equal-to-value.js +21 -0
  144. package/src/lib/utils/option-is-equal-to-value.js.map +1 -0
  145. package/src/lib/utils/validation-message.d.ts +22 -0
  146. package/src/lib/utils/validation-message.js +24 -0
  147. package/src/lib/utils/validation-message.js.map +1 -0
  148. package/src/lib/vendor/data-driven-forms.d.ts +33 -0
  149. package/src/lib/vendor/data-driven-forms.js +29 -0
  150. package/src/lib/vendor/data-driven-forms.js.map +1 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type FormFieldGridProps } from './form-field-grid';
18
+ import type { BaseFieldProps } from './types';
19
+ /**
20
+ * PresetChoice (AGL-2486): a named-preset picker with a raw escape hatch, for
21
+ * the properties whose honest answer is "pick one of these five" but whose CSS
22
+ * is a grammar.
23
+ *
24
+ * Three fields in the styles panel have that shape. Corner Radius as a text
25
+ * box can only offer `8px, 50%, or a theme spacing number` — three value
26
+ * systems in one helper line. A Shadow select with no escape hatch has to send
27
+ * the author to the custom-CSS section for anything off the list. Font Family
28
+ * as free text can advise "prefer theme typography" and give no way to act
29
+ * on it.
30
+ *
31
+ * All three are built here as: **the theme's own answers first, then a short
32
+ * list of plain-English presets, then Custom…** — and the preset shows what it
33
+ * looks like rather than what to type.
34
+ *
35
+ * **The persisted value is whatever the property already stored** — a
36
+ * number for the theme-multiple properties, a CSS string otherwise. This is
37
+ * an input affordance, not a shape change.
38
+ *
39
+ * **A stored value that matches no preset opens the field in its custom
40
+ * state showing that value**, so a hand-authored `box-shadow` or font stack
41
+ * round-trips and keeps rendering. That is the rule for every editor in
42
+ * this panel: the mode is DERIVED from the value, never remembered in a
43
+ * flag, so an author who typed raw CSS last month is never told their value
44
+ * is gone.
45
+ */
46
+ /** One offered preset. */
47
+ export interface PresetChoiceOption {
48
+ /**
49
+ * The value STORED.
50
+ *
51
+ * A NUMBER where the property's bare number is a theme multiple —
52
+ * `borderRadius: 2` renders 8px through `shape.borderRadius`, and the
53
+ * string `'2'` would reach CSS as `border-radius: 2` and be dropped. The
54
+ * panel's own `normalizeStyleValue` enforces the same rule one level up;
55
+ * emitting the number here means it never has to rescue this field.
56
+ */
57
+ value: string | number;
58
+ /** The author's name for it (`Rounded`, `Soft shadow`, `Sans-serif`). */
59
+ label: string;
60
+ /** What it resolves to in this theme today (`8px`, `system default`). */
61
+ hint?: string;
62
+ /**
63
+ * CSS the menu row renders ITSELF with, so the choice is made by looking
64
+ * rather than by reading. See {@link PresetChoiceProps.previewKind}.
65
+ */
66
+ preview?: string;
67
+ }
68
+ /** How a menu row shows what the preset does. */
69
+ export type PresetChoicePreview = 'none' | 'shadow' | 'radius' | 'font';
70
+ export interface PresetChoiceProps extends BaseFieldProps {
71
+ choices?: PresetChoiceOption[];
72
+ /**
73
+ * What the preview swatch on each row demonstrates. `font` renders the
74
+ * option's own label in that face — the only preview that tells a
75
+ * non-developer anything about a font stack. `shadow` and `radius` draw a
76
+ * small tile carrying the value.
77
+ */
78
+ previewKind?: PresetChoicePreview;
79
+ /** Label of the escape-hatch entry; defaults to `Custom…`. */
80
+ customLabel?: string;
81
+ /** Helper shown while the raw text box is open. */
82
+ customHelperText?: string;
83
+ placeholder?: string;
84
+ /** Offer the reset-to-unset affordance (AGL-2486). */
85
+ clearable?: boolean;
86
+ FormFieldGridProps?: FormFieldGridProps;
87
+ }
88
+ /** Sentinel for the escape-hatch entry. Never a stored value. */
89
+ export declare const PRESET_CHOICE_CUSTOM = "__custom__";
90
+ /** The text a stored value shows in the raw box. */
91
+ export declare const presetChoiceValueToText: (value: unknown) => string;
92
+ /**
93
+ * The preset a stored value names, compared as TEXT.
94
+ *
95
+ * A stored `2` and an option whose value is the number `2` are the same
96
+ * choice, and so are a stored `'50%'` and the string option — but the form
97
+ * value round-trips through controls that can only hand back strings, so
98
+ * comparing by identity would show "Custom" for a value the author picked
99
+ * from this very menu one render ago.
100
+ */
101
+ export declare const findPresetChoice: (choices: PresetChoiceOption[], value: unknown) => PresetChoiceOption | undefined;
102
+ export declare const PresetChoiceField: {
103
+ (props: PresetChoiceProps): import("react").JSX.Element;
104
+ displayName: string;
105
+ };
106
+ export default PresetChoiceField;
@@ -0,0 +1,267 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ import { Box, MenuItem, TextField as MuiTextField, Typography } from "@mui/material";
20
+ import { useCallback, useState } from "react";
21
+ import { useFieldApi } from "../vendor/data-driven-forms.js";
22
+ import FormFieldGrid, { buildFieldClear } from "./form-field-grid.js";
23
+ import { validationError } from "./validation-error.js";
24
+ /** Sentinel for the escape-hatch entry. Never a stored value. */ export const PRESET_CHOICE_CUSTOM = '__custom__';
25
+ /** The text a stored value shows in the raw box. */ export const presetChoiceValueToText = (value)=>{
26
+ if (value === undefined || value === null) return '';
27
+ if (typeof value === 'number') return Number.isFinite(value) ? `${value}` : '';
28
+ return `${value}`;
29
+ };
30
+ /**
31
+ * The preset a stored value names, compared as TEXT.
32
+ *
33
+ * A stored `2` and an option whose value is the number `2` are the same
34
+ * choice, and so are a stored `'50%'` and the string option — but the form
35
+ * value round-trips through controls that can only hand back strings, so
36
+ * comparing by identity would show "Custom" for a value the author picked
37
+ * from this very menu one render ago.
38
+ */ export const findPresetChoice = (choices, value)=>{
39
+ const text = presetChoiceValueToText(value);
40
+ if (text === '') return undefined;
41
+ return choices.find((choice)=>presetChoiceValueToText(choice.value) === text);
42
+ };
43
+ /** The swatch that shows what a preset does, or null when it shows nothing. */ const PresetPreview = (props)=>{
44
+ var _option_preview;
45
+ const { kind, option } = props;
46
+ const preview = (_option_preview = option.preview) != null ? _option_preview : presetChoiceValueToText(option.value);
47
+ if (kind === 'none' || kind === 'font' || preview === '') return null;
48
+ return /*#__PURE__*/ _jsx(Box, {
49
+ "aria-hidden": true,
50
+ sx: _extends({
51
+ width: 26,
52
+ height: 18,
53
+ flexShrink: 0,
54
+ // A neutral tile the effect is applied TO. `background.paper`
55
+ // rather than a literal so the swatch stays legible in both
56
+ // schemes — the console chrome is theme-aware here.
57
+ backgroundColor: 'background.paper',
58
+ border: 1,
59
+ borderColor: 'divider'
60
+ }, kind === 'shadow' ? {
61
+ boxShadow: preview === 'none' ? 'none' : preview
62
+ } : {
63
+ borderRadius: preview
64
+ })
65
+ });
66
+ };
67
+ export const PresetChoiceField = (props)=>{
68
+ const _useFieldApi = useFieldApi(props), { input, isReadOnly, isDisabled, isRequired, label, placeholder, helperText, description, validateOnMount, meta, help, choices = [], previewKind = 'none', customLabel = 'Custom…', customHelperText, clearable, FormFieldGridProps = {}, // Schema leftovers from the TEXT_FIELD/SELECT these were authored as.
69
+ inputProps: _inputProps, InputProps: _InputProps, multiline: _multiline, type: _type, options: _options } = _useFieldApi, rest = _object_without_properties_loose(_useFieldApi, [
70
+ "input",
71
+ "isReadOnly",
72
+ "isDisabled",
73
+ "isRequired",
74
+ "label",
75
+ "placeholder",
76
+ "helperText",
77
+ "description",
78
+ "validateOnMount",
79
+ "meta",
80
+ "help",
81
+ "choices",
82
+ "previewKind",
83
+ "customLabel",
84
+ "customHelperText",
85
+ "clearable",
86
+ "FormFieldGridProps",
87
+ "inputProps",
88
+ "InputProps",
89
+ "multiline",
90
+ "type",
91
+ "options"
92
+ ]);
93
+ const invalid = validationError(meta, validateOnMount);
94
+ const text = presetChoiceValueToText(input.value);
95
+ const matched = findPresetChoice(choices, input.value);
96
+ /**
97
+ * The raw box is open because the AUTHOR asked for it.
98
+ *
99
+ * The other reason it opens — a stored value that matches no preset — is
100
+ * DERIVED below rather than mirrored into this flag, and that distinction
101
+ * is load-bearing. `choices` come from the site theme, which resolves
102
+ * ASYNCHRONOUSLY: the styles panel mounts before `useHostThemeDocument`
103
+ * answers, so on first render every preset list is empty and every stored
104
+ * value "matches no preset". A remembered flag would latch that and every
105
+ * preset field would sit in its custom state showing a raw `2` for the
106
+ * rest of the session, with the real menu one render away and unreachable
107
+ * — a mutation test caught exactly this.
108
+ *
109
+ * Deriving instead makes the control self-healing: the moment the theme
110
+ * arrives and the value matches, the preset menu takes over on its own.
111
+ * The flag is only ever set by a click on Custom…, and cleared by
112
+ * choosing a preset or by clearing the field, so it can never disagree
113
+ * with a value it did not cause.
114
+ */ const [customPicked, setCustomPicked] = useState(false);
115
+ const custom = customPicked || text !== '' && !matched;
116
+ const commit = useCallback((next)=>{
117
+ input.onChange(next);
118
+ }, [
119
+ input
120
+ ]);
121
+ const clear = buildFieldClear({
122
+ clearable,
123
+ label,
124
+ hasValue: text !== '',
125
+ locked: Boolean(isDisabled || isReadOnly),
126
+ onClear: ()=>{
127
+ setCustomPicked(false);
128
+ commit('');
129
+ }
130
+ });
131
+ const handleSelect = useCallback((event)=>{
132
+ var _event_target_value;
133
+ const picked = `${(_event_target_value = event.target.value) != null ? _event_target_value : ''}`;
134
+ if (picked === PRESET_CHOICE_CUSTOM) {
135
+ // Opening the raw box must NOT clear what is already there: the
136
+ // commonest reason to open it is to tweak a preset by hand.
137
+ setCustomPicked(true);
138
+ return;
139
+ }
140
+ setCustomPicked(false);
141
+ const choice = choices.find((entry)=>presetChoiceValueToText(entry.value) === picked);
142
+ // The option's own `value` is emitted, not the select's string, so a
143
+ // numeric preset stays a NUMBER and keeps its theme-multiple meaning.
144
+ commit(choice ? choice.value : '');
145
+ }, [
146
+ choices,
147
+ commit
148
+ ]);
149
+ const selectValue = custom ? PRESET_CHOICE_CUSTOM : matched ? presetChoiceValueToText(matched.value) : '';
150
+ return /*#__PURE__*/ _jsxs(FormFieldGrid, _extends({
151
+ help: help,
152
+ clear: clear
153
+ }, FormFieldGridProps, {
154
+ children: [
155
+ /*#__PURE__*/ _jsxs(MuiTextField, _extends({}, rest, {
156
+ select: true,
157
+ name: custom ? `${input.name}-preset` : input.name,
158
+ value: selectValue,
159
+ onChange: handleSelect,
160
+ fullWidth: true,
161
+ size: "small",
162
+ margin: "dense",
163
+ error: !!invalid && !custom,
164
+ helperText: custom ? undefined : invalid || (meta.touched || validateOnMount) && meta.warning || // Naming what the preset resolves to is what makes it
165
+ // checkable: `Rounded` means nothing until it says `8px`.
166
+ ((matched == null ? void 0 : matched.hint) ? `${matched.label} — ${matched.hint}` : undefined) || helperText || description,
167
+ disabled: isDisabled,
168
+ label: label,
169
+ required: isRequired,
170
+ slotProps: {
171
+ select: {
172
+ displayEmpty: true
173
+ },
174
+ // `displayEmpty` renders "Not set" in the box while the value is
175
+ // empty, and MUI only floats a label once it thinks there IS a
176
+ // value — so the label sat ON the placeholder. Pinning it shrunk
177
+ // is the documented pairing for `displayEmpty` and is the same
178
+ // fix `CssDimension` carries for its keyword values (AGL-2486).
179
+ inputLabel: {
180
+ shrink: true
181
+ }
182
+ },
183
+ children: [
184
+ /*#__PURE__*/ _jsx(MenuItem, {
185
+ value: "",
186
+ children: /*#__PURE__*/ _jsx("em", {
187
+ children: 'Not set'
188
+ })
189
+ }),
190
+ choices.map((choice)=>{
191
+ var _choice_preview;
192
+ return /*#__PURE__*/ _jsx(MenuItem, {
193
+ value: presetChoiceValueToText(choice.value),
194
+ children: /*#__PURE__*/ _jsxs(Box, {
195
+ sx: {
196
+ display: 'flex',
197
+ alignItems: 'center',
198
+ gap: 1,
199
+ width: '100%',
200
+ minWidth: 0
201
+ },
202
+ children: [
203
+ /*#__PURE__*/ _jsx(PresetPreview, {
204
+ kind: previewKind,
205
+ option: choice
206
+ }),
207
+ /*#__PURE__*/ _jsx(Typography, {
208
+ variant: "body2",
209
+ noWrap: true,
210
+ sx: _extends({
211
+ flexGrow: 1,
212
+ minWidth: 0
213
+ }, previewKind === 'font' ? {
214
+ fontFamily: (_choice_preview = choice.preview) != null ? _choice_preview : presetChoiceValueToText(choice.value)
215
+ } : {}),
216
+ children: choice.label
217
+ }),
218
+ choice.hint ? /*#__PURE__*/ _jsx(Typography, {
219
+ variant: "caption",
220
+ color: "text.secondary",
221
+ noWrap: true,
222
+ children: choice.hint
223
+ }) : null
224
+ ]
225
+ })
226
+ }, presetChoiceValueToText(choice.value));
227
+ }),
228
+ /*#__PURE__*/ _jsx(MenuItem, {
229
+ value: PRESET_CHOICE_CUSTOM,
230
+ children: /*#__PURE__*/ _jsx("em", {
231
+ children: customLabel
232
+ })
233
+ })
234
+ ]
235
+ })),
236
+ custom ? /*#__PURE__*/ _jsx(MuiTextField, {
237
+ name: input.name,
238
+ value: text,
239
+ onChange: (event)=>commit(event.target.value),
240
+ onBlur: input.onBlur,
241
+ onFocus: input.onFocus,
242
+ fullWidth: true,
243
+ size: "small",
244
+ margin: "dense",
245
+ error: !!invalid,
246
+ helperText: invalid || (meta.touched || validateOnMount) && meta.warning || customHelperText || helperText || description,
247
+ disabled: isDisabled,
248
+ placeholder: placeholder,
249
+ slotProps: {
250
+ htmlInput: {
251
+ readOnly: isReadOnly,
252
+ // The visible label belongs to the SELECT — it names the
253
+ // field — so the raw box needs an accessible name of its
254
+ // own, or it is an unlabelled input that screen readers and
255
+ // tests both read as anonymous. Qualified rather than
256
+ // duplicated, because both are on screen at once.
257
+ 'aria-label': typeof label === 'string' ? `${label} custom value` : `${input.name} custom value`
258
+ }
259
+ }
260
+ }) : null
261
+ ]
262
+ }));
263
+ };
264
+ PresetChoiceField.displayName = 'AglynPresetChoiceField';
265
+ export default PresetChoiceField;
266
+
267
+ //# sourceMappingURL=preset-choice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx-forms/src/lib/mapper/preset-choice.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Box,\n MenuItem,\n TextField as MuiTextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useState } from 'react'\n\nimport { useFieldApi } from '../vendor/data-driven-forms'\nimport FormFieldGrid, {\n buildFieldClear,\n type FormFieldGridProps,\n} from './form-field-grid'\nimport type { BaseFieldProps } from './types'\nimport { type ExtendedFieldMeta, validationError } from './validation-error'\n\n/**\n * PresetChoice (AGL-2486): a named-preset picker with a raw escape hatch, for\n * the properties whose honest answer is \"pick one of these five\" but whose CSS\n * is a grammar.\n *\n * Three fields in the styles panel have that shape. Corner Radius as a text\n * box can only offer `8px, 50%, or a theme spacing number` — three value\n * systems in one helper line. A Shadow select with no escape hatch has to send\n * the author to the custom-CSS section for anything off the list. Font Family\n * as free text can advise \"prefer theme typography\" and give no way to act\n * on it.\n *\n * All three are built here as: **the theme's own answers first, then a short\n * list of plain-English presets, then Custom…** — and the preset shows what it\n * looks like rather than what to type.\n *\n * **The persisted value is whatever the property already stored** — a\n * number for the theme-multiple properties, a CSS string otherwise. This is\n * an input affordance, not a shape change.\n *\n * **A stored value that matches no preset opens the field in its custom\n * state showing that value**, so a hand-authored `box-shadow` or font stack\n * round-trips and keeps rendering. That is the rule for every editor in\n * this panel: the mode is DERIVED from the value, never remembered in a\n * flag, so an author who typed raw CSS last month is never told their value\n * is gone.\n */\n\n/** One offered preset. */\nexport interface PresetChoiceOption {\n /**\n * The value STORED.\n *\n * A NUMBER where the property's bare number is a theme multiple —\n * `borderRadius: 2` renders 8px through `shape.borderRadius`, and the\n * string `'2'` would reach CSS as `border-radius: 2` and be dropped. The\n * panel's own `normalizeStyleValue` enforces the same rule one level up;\n * emitting the number here means it never has to rescue this field.\n */\n value: string | number\n /** The author's name for it (`Rounded`, `Soft shadow`, `Sans-serif`). */\n label: string\n /** What it resolves to in this theme today (`8px`, `system default`). */\n hint?: string\n /**\n * CSS the menu row renders ITSELF with, so the choice is made by looking\n * rather than by reading. See {@link PresetChoiceProps.previewKind}.\n */\n preview?: string\n}\n\n/** How a menu row shows what the preset does. */\nexport type PresetChoicePreview = 'none' | 'shadow' | 'radius' | 'font'\n\nexport interface PresetChoiceProps extends BaseFieldProps {\n choices?: PresetChoiceOption[]\n /**\n * What the preview swatch on each row demonstrates. `font` renders the\n * option's own label in that face — the only preview that tells a\n * non-developer anything about a font stack. `shadow` and `radius` draw a\n * small tile carrying the value.\n */\n previewKind?: PresetChoicePreview\n /** Label of the escape-hatch entry; defaults to `Custom…`. */\n customLabel?: string\n /** Helper shown while the raw text box is open. */\n customHelperText?: string\n placeholder?: string\n /** Offer the reset-to-unset affordance (AGL-2486). */\n clearable?: boolean\n FormFieldGridProps?: FormFieldGridProps\n}\n\n/** Sentinel for the escape-hatch entry. Never a stored value. */\nexport const PRESET_CHOICE_CUSTOM = '__custom__'\n\n/** The text a stored value shows in the raw box. */\nexport const presetChoiceValueToText = (value: unknown): string => {\n if (value === undefined || value === null) return ''\n if (typeof value === 'number') return Number.isFinite(value) ? `${value}` : ''\n return `${value}`\n}\n\n/**\n * The preset a stored value names, compared as TEXT.\n *\n * A stored `2` and an option whose value is the number `2` are the same\n * choice, and so are a stored `'50%'` and the string option — but the form\n * value round-trips through controls that can only hand back strings, so\n * comparing by identity would show \"Custom\" for a value the author picked\n * from this very menu one render ago.\n */\nexport const findPresetChoice = (\n choices: PresetChoiceOption[],\n value: unknown,\n): PresetChoiceOption | undefined => {\n const text = presetChoiceValueToText(value)\n if (text === '') return undefined\n return choices.find(\n (choice) => presetChoiceValueToText(choice.value) === text,\n )\n}\n\n/** The swatch that shows what a preset does, or null when it shows nothing. */\nconst PresetPreview = (props: {\n kind: PresetChoicePreview\n option: PresetChoiceOption\n}) => {\n const { kind, option } = props\n const preview = option.preview ?? presetChoiceValueToText(option.value)\n if (kind === 'none' || kind === 'font' || preview === '') return null\n return (\n <Box\n aria-hidden\n sx={{\n width: 26,\n height: 18,\n flexShrink: 0,\n // A neutral tile the effect is applied TO. `background.paper`\n // rather than a literal so the swatch stays legible in both\n // schemes — the console chrome is theme-aware here.\n backgroundColor: 'background.paper',\n border: 1,\n borderColor: 'divider',\n ...(kind === 'shadow'\n ? { boxShadow: preview === 'none' ? 'none' : preview }\n : { borderRadius: preview }),\n }}\n />\n )\n}\n\nexport const PresetChoiceField = (props: PresetChoiceProps) => {\n const {\n input,\n isReadOnly,\n isDisabled,\n isRequired,\n label,\n placeholder,\n helperText,\n description,\n validateOnMount,\n meta,\n help,\n choices = [],\n previewKind = 'none',\n customLabel = 'Custom…',\n customHelperText,\n clearable,\n FormFieldGridProps = {},\n // Schema leftovers from the TEXT_FIELD/SELECT these were authored as.\n inputProps: _inputProps,\n InputProps: _InputProps,\n multiline: _multiline,\n type: _type,\n options: _options,\n ...rest\n } = useFieldApi(props)\n\n const invalid = validationError(meta as ExtendedFieldMeta, validateOnMount)\n const text = presetChoiceValueToText(input.value)\n const matched = findPresetChoice(choices, input.value)\n\n /**\n * The raw box is open because the AUTHOR asked for it.\n *\n * The other reason it opens — a stored value that matches no preset — is\n * DERIVED below rather than mirrored into this flag, and that distinction\n * is load-bearing. `choices` come from the site theme, which resolves\n * ASYNCHRONOUSLY: the styles panel mounts before `useHostThemeDocument`\n * answers, so on first render every preset list is empty and every stored\n * value \"matches no preset\". A remembered flag would latch that and every\n * preset field would sit in its custom state showing a raw `2` for the\n * rest of the session, with the real menu one render away and unreachable\n * — a mutation test caught exactly this.\n *\n * Deriving instead makes the control self-healing: the moment the theme\n * arrives and the value matches, the preset menu takes over on its own.\n * The flag is only ever set by a click on Custom…, and cleared by\n * choosing a preset or by clearing the field, so it can never disagree\n * with a value it did not cause.\n */\n const [customPicked, setCustomPicked] = useState(false)\n const custom = customPicked || (text !== '' && !matched)\n\n const commit = useCallback(\n (next: string | number) => {\n input.onChange(next)\n },\n [input],\n )\n\n const clear = buildFieldClear({\n clearable,\n label,\n hasValue: text !== '',\n locked: Boolean(isDisabled || isReadOnly),\n onClear: () => {\n setCustomPicked(false)\n commit('')\n },\n })\n\n const handleSelect = useCallback(\n (event: { target: { value: unknown } }) => {\n const picked = `${event.target.value ?? ''}`\n if (picked === PRESET_CHOICE_CUSTOM) {\n // Opening the raw box must NOT clear what is already there: the\n // commonest reason to open it is to tweak a preset by hand.\n setCustomPicked(true)\n return\n }\n setCustomPicked(false)\n const choice = choices.find(\n (entry) => presetChoiceValueToText(entry.value) === picked,\n )\n // The option's own `value` is emitted, not the select's string, so a\n // numeric preset stays a NUMBER and keeps its theme-multiple meaning.\n commit(choice ? choice.value : '')\n },\n [choices, commit],\n )\n\n const selectValue = custom\n ? PRESET_CHOICE_CUSTOM\n : matched\n ? presetChoiceValueToText(matched.value)\n : ''\n\n return (\n <FormFieldGrid help={help} clear={clear} {...FormFieldGridProps}>\n <MuiTextField\n {...rest}\n select\n name={custom ? `${input.name}-preset` : input.name}\n value={selectValue}\n onChange={handleSelect}\n fullWidth\n size=\"small\"\n margin=\"dense\"\n error={!!invalid && !custom}\n helperText={\n custom\n ? undefined\n : invalid ||\n ((meta.touched || validateOnMount) && meta.warning) ||\n // Naming what the preset resolves to is what makes it\n // checkable: `Rounded` means nothing until it says `8px`.\n (matched?.hint\n ? `${matched.label} — ${matched.hint}`\n : undefined) ||\n helperText ||\n description\n }\n disabled={isDisabled}\n label={label}\n required={isRequired}\n slotProps={{\n select: { displayEmpty: true },\n // `displayEmpty` renders \"Not set\" in the box while the value is\n // empty, and MUI only floats a label once it thinks there IS a\n // value — so the label sat ON the placeholder. Pinning it shrunk\n // is the documented pairing for `displayEmpty` and is the same\n // fix `CssDimension` carries for its keyword values (AGL-2486).\n inputLabel: { shrink: true },\n }}\n >\n <MenuItem value=\"\">\n <em>{'Not set'}</em>\n </MenuItem>\n {choices.map((choice) => (\n <MenuItem\n key={presetChoiceValueToText(choice.value)}\n value={presetChoiceValueToText(choice.value)}\n >\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 1,\n width: '100%',\n minWidth: 0,\n }}\n >\n <PresetPreview kind={previewKind} option={choice} />\n <Typography\n variant=\"body2\"\n noWrap\n sx={{\n flexGrow: 1,\n minWidth: 0,\n // A font preset renders its own name in its own face —\n // the only preview that says anything useful about a\n // typeface to someone who cannot read a font stack.\n ...(previewKind === 'font'\n ? {\n fontFamily:\n choice.preview ??\n presetChoiceValueToText(choice.value),\n }\n : {}),\n }}\n >\n {choice.label}\n </Typography>\n {choice.hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {choice.hint}\n </Typography>\n ) : null}\n </Box>\n </MenuItem>\n ))}\n <MenuItem value={PRESET_CHOICE_CUSTOM}>\n <em>{customLabel}</em>\n </MenuItem>\n </MuiTextField>\n {custom ? (\n <MuiTextField\n name={input.name}\n value={text}\n onChange={(event) => commit(event.target.value)}\n onBlur={input.onBlur}\n onFocus={input.onFocus}\n fullWidth\n size=\"small\"\n margin=\"dense\"\n error={!!invalid}\n helperText={\n invalid ||\n ((meta.touched || validateOnMount) && meta.warning) ||\n customHelperText ||\n helperText ||\n description\n }\n disabled={isDisabled}\n placeholder={placeholder}\n slotProps={{\n htmlInput: {\n readOnly: isReadOnly,\n // The visible label belongs to the SELECT — it names the\n // field — so the raw box needs an accessible name of its\n // own, or it is an unlabelled input that screen readers and\n // tests both read as anonymous. Qualified rather than\n // duplicated, because both are on screen at once.\n 'aria-label':\n typeof label === 'string'\n ? `${label} custom value`\n : `${input.name} custom value`,\n },\n }}\n />\n ) : null}\n </FormFieldGrid>\n )\n}\nPresetChoiceField.displayName = 'AglynPresetChoiceField'\n\nexport default PresetChoiceField\n"],"names":["Box","MenuItem","TextField","MuiTextField","Typography","useCallback","useState","useFieldApi","FormFieldGrid","buildFieldClear","validationError","PRESET_CHOICE_CUSTOM","presetChoiceValueToText","value","undefined","Number","isFinite","findPresetChoice","choices","text","find","choice","PresetPreview","props","option","kind","preview","aria-hidden","sx","width","height","flexShrink","backgroundColor","border","borderColor","boxShadow","borderRadius","PresetChoiceField","input","isReadOnly","isDisabled","isRequired","label","placeholder","helperText","description","validateOnMount","meta","help","previewKind","customLabel","customHelperText","clearable","FormFieldGridProps","inputProps","_inputProps","InputProps","_InputProps","multiline","_multiline","type","_type","options","_options","rest","invalid","matched","customPicked","setCustomPicked","custom","commit","next","onChange","clear","hasValue","locked","Boolean","onClear","handleSelect","event","picked","target","entry","selectValue","select","name","fullWidth","size","margin","error","touched","warning","hint","disabled","required","slotProps","displayEmpty","inputLabel","shrink","em","map","display","alignItems","gap","minWidth","variant","noWrap","flexGrow","fontFamily","color","onBlur","onFocus","htmlInput","readOnly","displayName"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,GAAG,EACHC,QAAQ,EACRC,aAAaC,YAAY,EACzBC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAE7C,SAASC,WAAW,QAAQ,iCAA6B;AACzD,OAAOC,iBACLC,eAAe,QAEV,uBAAmB;AAE1B,SAAiCC,eAAe,QAAQ,wBAAoB;AA2E5E,+DAA+D,GAC/D,OAAO,MAAMC,uBAAuB,aAAY;AAEhD,kDAAkD,GAClD,OAAO,MAAMC,0BAA0B,CAACC;IACtC,IAAIA,UAAUC,aAAaD,UAAU,MAAM,OAAO;IAClD,IAAI,OAAOA,UAAU,UAAU,OAAOE,OAAOC,QAAQ,CAACH,SAAS,GAAGA,OAAO,GAAG;IAC5E,OAAO,GAAGA,OAAO;AACnB,EAAC;AAED;;;;;;;;CAQC,GACD,OAAO,MAAMI,mBAAmB,CAC9BC,SACAL;IAEA,MAAMM,OAAOP,wBAAwBC;IACrC,IAAIM,SAAS,IAAI,OAAOL;IACxB,OAAOI,QAAQE,IAAI,CACjB,CAACC,SAAWT,wBAAwBS,OAAOR,KAAK,MAAMM;AAE1D,EAAC;AAED,6EAA6E,GAC7E,MAAMG,gBAAgB,CAACC;QAKLC;IADhB,MAAM,EAAEC,IAAI,EAAED,MAAM,EAAE,GAAGD;IACzB,MAAMG,WAAUF,kBAAAA,OAAOE,OAAO,YAAdF,kBAAkBZ,wBAAwBY,OAAOX,KAAK;IACtE,IAAIY,SAAS,UAAUA,SAAS,UAAUC,YAAY,IAAI,OAAO;IACjE,qBACE,KAAC1B;QACC2B,aAAW;QACXC,IAAI;YACFC,OAAO;YACPC,QAAQ;YACRC,YAAY;YACZ,8DAA8D;YAC9D,4DAA4D;YAC5D,oDAAoD;YACpDC,iBAAiB;YACjBC,QAAQ;YACRC,aAAa;WACTT,SAAS,WACT;YAAEU,WAAWT,YAAY,SAAS,SAASA;QAAQ,IACnD;YAAEU,cAAcV;QAAQ;;AAIpC;AAEA,OAAO,MAAMW,oBAAoB,CAACd;IAChC,MAyBIhB,eAAAA,YAAYgB,QAzBV,EACJe,KAAK,EACLC,UAAU,EACVC,UAAU,EACVC,UAAU,EACVC,KAAK,EACLC,WAAW,EACXC,UAAU,EACVC,WAAW,EACXC,eAAe,EACfC,IAAI,EACJC,IAAI,EACJ9B,UAAU,EAAE,EACZ+B,cAAc,MAAM,EACpBC,cAAc,SAAS,EACvBC,gBAAgB,EAChBC,SAAS,EACTC,qBAAqB,CAAC,CAAC,EACvB,sEAAsE;IACtEC,YAAYC,WAAW,EACvBC,YAAYC,WAAW,EACvBC,WAAWC,UAAU,EACrBC,MAAMC,KAAK,EACXC,SAASC,QAAQ,EAElB,GAAGxD,cADCyD,wCACDzD;;;;;;;;;;;;;;;;;;;;;;;;IAEJ,MAAM0D,UAAUvD,gBAAgBqC,MAA2BD;IAC3D,MAAM3B,OAAOP,wBAAwB0B,MAAMzB,KAAK;IAChD,MAAMqD,UAAUjD,iBAAiBC,SAASoB,MAAMzB,KAAK;IAErD;;;;;;;;;;;;;;;;;;GAkBC,GACD,MAAM,CAACsD,cAAcC,gBAAgB,GAAG9D,SAAS;IACjD,MAAM+D,SAASF,gBAAiBhD,SAAS,MAAM,CAAC+C;IAEhD,MAAMI,SAASjE,YACb,CAACkE;QACCjC,MAAMkC,QAAQ,CAACD;IACjB,GACA;QAACjC;KAAM;IAGT,MAAMmC,QAAQhE,gBAAgB;QAC5B2C;QACAV;QACAgC,UAAUvD,SAAS;QACnBwD,QAAQC,QAAQpC,cAAcD;QAC9BsC,SAAS;YACPT,gBAAgB;YAChBE,OAAO;QACT;IACF;IAEA,MAAMQ,eAAezE,YACnB,CAAC0E;YACmBA;QAAlB,MAAMC,SAAS,IAAGD,sBAAAA,MAAME,MAAM,CAACpE,KAAK,YAAlBkE,sBAAsB,IAAI;QAC5C,IAAIC,WAAWrE,sBAAsB;YACnC,gEAAgE;YAChE,4DAA4D;YAC5DyD,gBAAgB;YAChB;QACF;QACAA,gBAAgB;QAChB,MAAM/C,SAASH,QAAQE,IAAI,CACzB,CAAC8D,QAAUtE,wBAAwBsE,MAAMrE,KAAK,MAAMmE;QAEtD,qEAAqE;QACrE,sEAAsE;QACtEV,OAAOjD,SAASA,OAAOR,KAAK,GAAG;IACjC,GACA;QAACK;QAASoD;KAAO;IAGnB,MAAMa,cAAcd,SAChB1D,uBACAuD,UACEtD,wBAAwBsD,QAAQrD,KAAK,IACrC;IAEN,qBACE,MAACL;QAAcwC,MAAMA;QAAMyB,OAAOA;OAAWpB;;0BAC3C,MAAClD,2BACK6D;gBACJoB,MAAM;gBACNC,MAAMhB,SAAS,GAAG/B,MAAM+C,IAAI,CAAC,OAAO,CAAC,GAAG/C,MAAM+C,IAAI;gBAClDxE,OAAOsE;gBACPX,UAAUM;gBACVQ,SAAS;gBACTC,MAAK;gBACLC,QAAO;gBACPC,OAAO,CAAC,CAACxB,WAAW,CAACI;gBACrBzB,YACEyB,SACIvD,YACAmD,WACC,AAAClB,CAAAA,KAAK2C,OAAO,IAAI5C,eAAc,KAAMC,KAAK4C,OAAO,IAClD,sDAAsD;gBACtD,0DAA0D;gBACzDzB,CAAAA,CAAAA,2BAAAA,QAAS0B,IAAI,IACV,GAAG1B,QAAQxB,KAAK,CAAC,GAAG,EAAEwB,QAAQ0B,IAAI,EAAE,GACpC9E,SAAQ,KACZ8B,cACAC;gBAENgD,UAAUrD;gBACVE,OAAOA;gBACPoD,UAAUrD;gBACVsD,WAAW;oBACTX,QAAQ;wBAAEY,cAAc;oBAAK;oBAC7B,iEAAiE;oBACjE,+DAA+D;oBAC/D,iEAAiE;oBACjE,+DAA+D;oBAC/D,gEAAgE;oBAChEC,YAAY;wBAAEC,QAAQ;oBAAK;gBAC7B;;kCAEA,KAACjG;wBAASY,OAAM;kCACd,cAAA,KAACsF;sCAAI;;;oBAENjF,QAAQkF,GAAG,CAAC,CAAC/E;4BA2BIA;6CA1BhB,KAACpB;4BAECY,OAAOD,wBAAwBS,OAAOR,KAAK;sCAE3C,cAAA,MAACb;gCACC4B,IAAI;oCACFyE,SAAS;oCACTC,YAAY;oCACZC,KAAK;oCACL1E,OAAO;oCACP2E,UAAU;gCACZ;;kDAEA,KAAClF;wCAAcG,MAAMwB;wCAAazB,QAAQH;;kDAC1C,KAACjB;wCACCqG,SAAQ;wCACRC,MAAM;wCACN9E,IAAI;4CACF+E,UAAU;4CACVH,UAAU;2CAINvD,gBAAgB,SAChB;4CACE2D,UAAU,GACRvF,kBAAAA,OAAOK,OAAO,YAAdL,kBACAT,wBAAwBS,OAAOR,KAAK;wCACxC,IACA,CAAC;kDAGNQ,OAAOqB,KAAK;;oCAEdrB,OAAOuE,IAAI,iBACV,KAACxF;wCAAWqG,SAAQ;wCAAUI,OAAM;wCAAiBH,MAAM;kDACxDrF,OAAOuE,IAAI;yCAEZ;;;2BArCDhF,wBAAwBS,OAAOR,KAAK;;kCAyC7C,KAACZ;wBAASY,OAAOF;kCACf,cAAA,KAACwF;sCAAIjD;;;;;YAGRmB,uBACC,KAAClE;gBACCkF,MAAM/C,MAAM+C,IAAI;gBAChBxE,OAAOM;gBACPqD,UAAU,CAACO,QAAUT,OAAOS,MAAME,MAAM,CAACpE,KAAK;gBAC9CiG,QAAQxE,MAAMwE,MAAM;gBACpBC,SAASzE,MAAMyE,OAAO;gBACtBzB,SAAS;gBACTC,MAAK;gBACLC,QAAO;gBACPC,OAAO,CAAC,CAACxB;gBACTrB,YACEqB,WACC,AAAClB,CAAAA,KAAK2C,OAAO,IAAI5C,eAAc,KAAMC,KAAK4C,OAAO,IAClDxC,oBACAP,cACAC;gBAEFgD,UAAUrD;gBACVG,aAAaA;gBACboD,WAAW;oBACTiB,WAAW;wBACTC,UAAU1E;wBACV,yDAAyD;wBACzD,yDAAyD;wBACzD,4DAA4D;wBAC5D,sDAAsD;wBACtD,kDAAkD;wBAClD,cACE,OAAOG,UAAU,WACb,GAAGA,MAAM,aAAa,CAAC,GACvB,GAAGJ,MAAM+C,IAAI,CAAC,aAAa,CAAC;oBACpC;gBACF;iBAEA;;;AAGV,EAAC;AACDhD,kBAAkB6E,WAAW,GAAG;AAEhC,eAAe7E,kBAAiB"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Ported from `@data-driven-forms/mui-component-mapper` (Apache-2.0) and
19
+ * updated for the current MUI API (`slotProps` instead of `inputProps`).
20
+ */
21
+ import { type FormControlLabelProps, type FormControlProps, type FormHelperTextProps, type FormLabelProps } from '@mui/material';
22
+ import { type FormFieldGridProps } from './form-field-grid';
23
+ import type { BaseFieldProps, OptionValue, SelectOption } from './types';
24
+ interface RadioFieldOptionProps {
25
+ inputProps?: Record<string, any>;
26
+ [key: string]: any;
27
+ }
28
+ export interface RadioProps<T = OptionValue> extends BaseFieldProps {
29
+ options?: SelectOption<T>[];
30
+ /** Offer the reset-to-unset affordance (AGL-2486). */
31
+ clearable?: boolean;
32
+ FormFieldGridProps?: FormFieldGridProps;
33
+ FormControlProps?: FormControlProps;
34
+ FormLabelProps?: FormLabelProps;
35
+ FormHelperTextProps?: FormHelperTextProps;
36
+ FormControlLabelProps?: Partial<FormControlLabelProps>;
37
+ RadioProps?: RadioFieldOptionProps;
38
+ }
39
+ export declare function Radio<T = OptionValue>({ name, ...props }: RadioProps<T>): import("react").JSX.Element;
40
+ export default Radio;
@@ -0,0 +1,122 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ /**
20
+ * Ported from `@data-driven-forms/mui-component-mapper` (Apache-2.0) and
21
+ * updated for the current MUI API (`slotProps` instead of `inputProps`).
22
+ */ import { FormControl, FormControlLabel, FormHelperText, FormLabel, Radio as MuiRadio } from "@mui/material";
23
+ import { styled } from "@mui/material/styles";
24
+ import { useFieldApi } from "../vendor/data-driven-forms.js";
25
+ import FormFieldGrid, { buildFieldClear } from "./form-field-grid.js";
26
+ import { useStoredFieldHasValue } from "./stored-field-value.js";
27
+ import { validationError } from "./validation-error.js";
28
+ const PREFIX = 'Radio';
29
+ const classes = {
30
+ grid: `${PREFIX}-grid`
31
+ };
32
+ const StyledFormFieldGrid = styled(FormFieldGrid)(()=>({
33
+ [`&.${classes.grid}`]: {
34
+ '&:first-of-type': {
35
+ marginTop: 8
36
+ }
37
+ }
38
+ }));
39
+ function RadioOption(_0) {
40
+ let { name, option, isDisabled, isReadOnly, FormControlLabelProps, RadioProps: _ref = {} } = _0, { inputProps } = _ref, RadioProps = _object_without_properties_loose(_ref, [
41
+ "inputProps"
42
+ ]), props = _object_without_properties_loose(_0, [
43
+ "name",
44
+ "option",
45
+ "isDisabled",
46
+ "isReadOnly",
47
+ "FormControlLabelProps",
48
+ "RadioProps"
49
+ ]);
50
+ const { input } = useFieldApi({
51
+ name,
52
+ type: 'radio',
53
+ value: option.value
54
+ });
55
+ return /*#__PURE__*/ _jsx(FormControlLabel, _extends({
56
+ control: /*#__PURE__*/ _jsx(MuiRadio, _extends({}, input, {
57
+ name: name,
58
+ disabled: isDisabled || isReadOnly,
59
+ onChange: ()=>input.onChange(option.value),
60
+ slotProps: {
61
+ input: _extends({
62
+ readOnly: isReadOnly
63
+ }, inputProps)
64
+ }
65
+ }, RadioProps)),
66
+ label: option.label
67
+ }, FormControlLabelProps, props), `${name}-${String(option.value)}`);
68
+ }
69
+ export function Radio(_0) {
70
+ let { name } = _0, props = _object_without_properties_loose(_0, [
71
+ "name"
72
+ ]);
73
+ const { options = [], isDisabled, label, isRequired, helperText, description, isReadOnly, meta, validateOnMount, help, clearable, input, FormFieldGridProps = {}, FormControlProps = {}, FormLabelProps = {}, FormHelperTextProps = {}, FormControlLabelProps = {}, RadioProps = {} } = useFieldApi(_extends({}, props, {
74
+ name,
75
+ type: 'radio'
76
+ }));
77
+ const invalid = validationError(meta, validateOnMount);
78
+ const text = invalid || (meta.touched || validateOnMount) && meta.warning || helperText || description;
79
+ const hasValue = useStoredFieldHasValue(name);
80
+ // Each option is its own radio input, so the group has no control that can
81
+ // be driven back to "nothing chosen" — the clear button is that way back.
82
+ const clear = buildFieldClear({
83
+ clearable,
84
+ label,
85
+ hasValue,
86
+ locked: Boolean(isDisabled || isReadOnly),
87
+ onClear: ()=>input.onChange(undefined)
88
+ });
89
+ return /*#__PURE__*/ _jsx(StyledFormFieldGrid, _extends({
90
+ className: classes.grid,
91
+ help: help,
92
+ clear: clear
93
+ }, FormFieldGridProps, {
94
+ children: /*#__PURE__*/ _jsxs(FormControl, _extends({
95
+ required: isRequired,
96
+ error: !!invalid,
97
+ component: "fieldset"
98
+ }, FormControlProps, {
99
+ children: [
100
+ /*#__PURE__*/ _jsx(FormLabel, _extends({
101
+ component: "legend"
102
+ }, FormLabelProps, {
103
+ children: label
104
+ })),
105
+ options.map((option)=>/*#__PURE__*/ _jsx(RadioOption, {
106
+ name: name || '',
107
+ option: option,
108
+ isDisabled: isDisabled,
109
+ isReadOnly: isReadOnly,
110
+ FormControlLabelProps: FormControlLabelProps,
111
+ RadioProps: RadioProps
112
+ }, String(option.value))),
113
+ text && /*#__PURE__*/ _jsx(FormHelperText, _extends({}, FormHelperTextProps, {
114
+ children: text
115
+ }))
116
+ ]
117
+ }))
118
+ }));
119
+ }
120
+ export default Radio;
121
+
122
+ //# sourceMappingURL=radio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx-forms/src/lib/mapper/radio.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Ported from `@data-driven-forms/mui-component-mapper` (Apache-2.0) and\n * updated for the current MUI API (`slotProps` instead of `inputProps`).\n */\n\nimport {\n FormControl,\n type FormControlLabelProps,\n FormControlLabel,\n type FormControlProps,\n FormHelperText,\n type FormHelperTextProps,\n FormLabel,\n type FormLabelProps,\n Radio as MuiRadio,\n} from '@mui/material'\nimport { styled } from '@mui/material/styles'\n\nimport { useFieldApi } from '../vendor/data-driven-forms'\nimport FormFieldGrid, {\n buildFieldClear,\n type FormFieldGridProps,\n} from './form-field-grid'\nimport { useStoredFieldHasValue } from './stored-field-value'\nimport type { BaseFieldProps, OptionValue, SelectOption } from './types'\nimport { type ExtendedFieldMeta, validationError } from './validation-error'\n\nconst PREFIX = 'Radio'\n\nconst classes = {\n grid: `${PREFIX}-grid`,\n}\n\nconst StyledFormFieldGrid = styled(FormFieldGrid)(() => ({\n [`&.${classes.grid}`]: {\n '&:first-of-type': {\n marginTop: 8,\n },\n },\n}))\n\ninterface RadioFieldOptionProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n inputProps?: Record<string, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any\n}\n\ninterface RadioOptionProps<T = OptionValue> {\n name: string\n option: SelectOption<T>\n isDisabled?: boolean\n isReadOnly?: boolean\n FormControlLabelProps?: Partial<FormControlLabelProps>\n RadioProps?: RadioFieldOptionProps\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any\n}\n\nfunction RadioOption<T = OptionValue>({\n name,\n option,\n isDisabled,\n isReadOnly,\n FormControlLabelProps,\n RadioProps: { inputProps, ...RadioProps } = {},\n ...props\n}: RadioOptionProps<T>) {\n const { input } = useFieldApi({\n name,\n type: 'radio',\n value: option.value,\n })\n\n return (\n <FormControlLabel\n key={`${name}-${String(option.value)}`}\n control={\n <MuiRadio\n {...input}\n name={name}\n disabled={isDisabled || isReadOnly}\n onChange={() => input.onChange(option.value)}\n slotProps={{\n input: { readOnly: isReadOnly, ...inputProps },\n }}\n {...RadioProps}\n />\n }\n label={option.label}\n {...FormControlLabelProps}\n {...props}\n />\n )\n}\n\nexport interface RadioProps<T = OptionValue> extends BaseFieldProps {\n options?: SelectOption<T>[]\n /** Offer the reset-to-unset affordance (AGL-2486). */\n clearable?: boolean\n FormFieldGridProps?: FormFieldGridProps\n FormControlProps?: FormControlProps\n FormLabelProps?: FormLabelProps\n FormHelperTextProps?: FormHelperTextProps\n FormControlLabelProps?: Partial<FormControlLabelProps>\n RadioProps?: RadioFieldOptionProps\n}\n\nexport function Radio<T = OptionValue>({ name, ...props }: RadioProps<T>) {\n const {\n options = [],\n isDisabled,\n label,\n isRequired,\n helperText,\n description,\n isReadOnly,\n meta,\n validateOnMount,\n help,\n clearable,\n input,\n FormFieldGridProps = {},\n FormControlProps = {},\n FormLabelProps = {},\n FormHelperTextProps = {},\n FormControlLabelProps = {},\n RadioProps = {},\n } = useFieldApi({\n ...props,\n name,\n type: 'radio',\n })\n\n const invalid = validationError(meta as ExtendedFieldMeta, validateOnMount)\n const text =\n invalid ||\n ((meta.touched || validateOnMount) && meta.warning) ||\n helperText ||\n description\n const hasValue = useStoredFieldHasValue(name)\n // Each option is its own radio input, so the group has no control that can\n // be driven back to \"nothing chosen\" — the clear button is that way back.\n const clear = buildFieldClear({\n clearable,\n label,\n hasValue,\n locked: Boolean(isDisabled || isReadOnly),\n onClear: () => input.onChange(undefined),\n })\n\n return (\n <StyledFormFieldGrid\n className={classes.grid}\n help={help}\n clear={clear}\n {...FormFieldGridProps}\n >\n <FormControl\n required={isRequired}\n error={!!invalid}\n component=\"fieldset\"\n {...FormControlProps}\n >\n <FormLabel component=\"legend\" {...FormLabelProps}>\n {label}\n </FormLabel>\n {(options as SelectOption<T>[]).map((option) => (\n <RadioOption<T>\n key={String(option.value)}\n name={name || ''}\n option={option}\n isDisabled={isDisabled}\n isReadOnly={isReadOnly}\n FormControlLabelProps={FormControlLabelProps}\n RadioProps={RadioProps}\n />\n ))}\n {text && (\n <FormHelperText {...FormHelperTextProps}>{text}</FormHelperText>\n )}\n </FormControl>\n </StyledFormFieldGrid>\n )\n}\n\nexport default Radio\n"],"names":["FormControl","FormControlLabel","FormHelperText","FormLabel","Radio","MuiRadio","styled","useFieldApi","FormFieldGrid","buildFieldClear","useStoredFieldHasValue","validationError","PREFIX","classes","grid","StyledFormFieldGrid","marginTop","RadioOption","name","option","isDisabled","isReadOnly","FormControlLabelProps","RadioProps","inputProps","props","input","type","value","control","disabled","onChange","slotProps","readOnly","label","String","options","isRequired","helperText","description","meta","validateOnMount","help","clearable","FormFieldGridProps","FormControlProps","FormLabelProps","FormHelperTextProps","invalid","text","touched","warning","hasValue","clear","locked","Boolean","onClear","undefined","className","required","error","component","map"],"mappings":"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"}