@pienter/ui 0.3.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +258 -0
- package/CONVENTIONS.md +342 -385
- package/README.md +33 -18
- package/components/display/record-details/RecordDetails.vue +61 -0
- package/components/display/record-details/record-details.css +37 -0
- package/components/display/record-details/types.ts +8 -0
- package/components/feedback/toast/toast.css +1 -1
- package/components/form/block-editor/BlockEditor.vue +454 -0
- package/components/form/block-editor/block-editor.css +149 -0
- package/components/form/block-editor/types.ts +15 -0
- package/components/form/combobox/Combobox.vue +42 -72
- package/components/form/combobox/combobox.css +1 -1
- package/components/form/form/Form.vue +1 -2
- package/components/form/label/label.css +1 -1
- package/components/form/number-field/NumberField.vue +1 -2
- package/components/form/number-field/number-field.css +1 -1
- package/components/form/radio-group/RadioGroup.vue +2 -5
- package/components/form/record-form/RecordFields.vue +128 -0
- package/components/form/record-form/RecordForm.vue +116 -0
- package/components/form/record-form/fields.ts +20 -0
- package/components/form/record-form/record-form.css +15 -0
- package/components/form/record-form/types.ts +28 -0
- package/components/form/slider/slider.css +2 -3
- package/components/form/tags-input/tags-input.css +1 -2
- package/components/form/text-input/text-input.css +2 -0
- package/components/form/textarea/textarea.css +1 -1
- package/components/layout/accordion/Accordion.vue +6 -27
- package/components/layout/collapsible/Collapsible.vue +9 -19
- package/components/layout/index/Index.vue +353 -0
- package/components/layout/index/index.css +114 -0
- package/components/layout/index/useIndex.ts +390 -0
- package/components/layout/table/table.css +5 -4
- package/components/navigation/breadcrumb/Breadcrumb.vue +24 -5
- package/components/navigation/breadcrumb/breadcrumb.css +15 -0
- package/components/navigation/sidebar/Sidebar.vue +329 -84
- package/components/navigation/sidebar/SidebarMenuItem.vue +199 -0
- package/components/navigation/sidebar/context.ts +21 -0
- package/components/navigation/sidebar/sidebar.css +346 -78
- package/components/navigation/sidebar/types.ts +13 -1
- package/components/navigation/tabs/Tabs.vue +6 -0
- package/components/overlay/alert-dialog/AlertDialog.vue +10 -31
- package/components/overlay/command/Command.vue +10 -38
- package/components/overlay/command/command.css +1 -1
- package/components/overlay/dropdown-menu/DropdownMenu.vue +37 -62
- package/components/overlay/modal/Modal.vue +7 -28
- package/components/overlay/popover/Popover.vue +9 -31
- package/components/overlay/sheet/Sheet.vue +7 -28
- package/components/overlay/tooltip/Tooltip.vue +14 -19
- package/{utils/controllers/dialog.ts → composables/useDialog.ts} +90 -78
- package/composables/useDisclosure.ts +113 -0
- package/composables/useEventListener.ts +16 -0
- package/composables/useMenu.ts +212 -0
- package/{utils/controllers/popover.ts → composables/usePopover.ts} +107 -120
- package/package.json +16 -40
- package/styles/0-settings/colors.css +10 -0
- package/styles/0-settings/layout.css +18 -0
- package/styles/0-settings/motion.css +2 -2
- package/styles/0-settings/spacing.css +3 -1
- package/utils/a11y/focus.ts +9 -3
- package/utils/cms/index.ts +283 -0
- package/utils/cms/schema.json +126 -0
- package/utils/navigation/sidebar.ts +97 -0
- package/utils/validation/form.ts +6 -9
- package/components/action/button/Button.astro +0 -95
- package/components/action/button/IconButton.astro +0 -86
- package/components/display/avatar/Avatar.astro +0 -17
- package/components/display/avatar/AvatarStack.astro +0 -9
- package/components/display/badge/Badge.astro +0 -15
- package/components/display/empty/Empty.astro +0 -9
- package/components/display/icon/Icon.astro +0 -52
- package/components/feedback/alert/Alert.astro +0 -52
- package/components/feedback/progress/Progress.astro +0 -68
- package/components/feedback/skeleton/Skeleton.astro +0 -32
- package/components/feedback/spinner/Spinner.astro +0 -25
- package/components/feedback/toast/Toast.astro +0 -50
- package/components/form/checkbox/Checkbox.astro +0 -79
- package/components/form/date-input/DateInput.astro +0 -105
- package/components/form/form/Form.astro +0 -106
- package/components/form/input-otp/InputOTP.astro +0 -147
- package/components/form/label/Label.astro +0 -13
- package/components/form/number-field/NumberField.astro +0 -142
- package/components/form/radio-group/RadioGroup.astro +0 -105
- package/components/form/select/Select.astro +0 -105
- package/components/form/slider/Slider.astro +0 -205
- package/components/form/switch/Switch.astro +0 -75
- package/components/form/tags-input/TagsInput.astro +0 -153
- package/components/form/text-input/TextInput.astro +0 -84
- package/components/form/textarea/Textarea.astro +0 -86
- package/components/layout/card/Card.astro +0 -13
- package/components/layout/separator/Separator.astro +0 -31
- package/components/layout/table/Table.astro +0 -116
- package/components/navigation/breadcrumb/Breadcrumb.astro +0 -36
- package/components/navigation/navbar/Navbar.astro +0 -62
- package/components/navigation/pagination/PaginationFooter.astro +0 -24
- package/components/navigation/sidebar/Sidebar.astro +0 -132
- package/components/navigation/sidebar/SidebarItemRender.astro +0 -83
- package/components/navigation/sidebar/SidebarItemRender.vue +0 -98
- package/components/overlay/alert-dialog/AlertDialog.astro +0 -112
- package/components/overlay/modal/Modal.astro +0 -66
- package/utils/controllers/disclosure.ts +0 -117
- package/utils/controllers/form.ts +0 -524
- package/utils/controllers/index.ts +0 -39
- package/utils/controllers/menu.ts +0 -255
- package/utils/controllers/number-field.ts +0 -103
- package/utils/controllers/otp.ts +0 -252
- package/utils/controllers/sidebar.ts +0 -610
- package/utils/controllers/slider.ts +0 -336
- package/utils/controllers/tags-input.ts +0 -255
- package/utils/controllers/toast.ts +0 -426
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './form.css';
|
|
3
|
-
import { generateId } from '../../../utils/a11y/id.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
/** Stable form ID — used by the consumer's `mountForm` script. */
|
|
7
|
-
id?: string;
|
|
8
|
-
submitLabel?: string;
|
|
9
|
-
busy?: boolean;
|
|
10
|
-
/** Top-level errors rendered in the error summary. */
|
|
11
|
-
errors?: string[];
|
|
12
|
-
/** Success/status message rendered in the polite live region. */
|
|
13
|
-
statusMessage?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
id,
|
|
18
|
-
submitLabel = 'Submit',
|
|
19
|
-
busy = false,
|
|
20
|
-
errors = [],
|
|
21
|
-
statusMessage,
|
|
22
|
-
} = Astro.props;
|
|
23
|
-
|
|
24
|
-
const formId = id ?? generateId('form');
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
<form
|
|
28
|
-
class="pui-form"
|
|
29
|
-
id={formId}
|
|
30
|
-
data-pui-form
|
|
31
|
-
novalidate
|
|
32
|
-
aria-busy={busy ? 'true' : undefined}
|
|
33
|
-
>
|
|
34
|
-
{
|
|
35
|
-
errors.length > 0 && (
|
|
36
|
-
<div class="pui-form__error-summary" role="alert" aria-live="assertive">
|
|
37
|
-
<p class="pui-form__error-summary-title">There was a problem</p>
|
|
38
|
-
<ul class="pui-form__error-summary-list">
|
|
39
|
-
{errors.map((error) => (
|
|
40
|
-
<li>{error}</li>
|
|
41
|
-
))}
|
|
42
|
-
</ul>
|
|
43
|
-
</div>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
<slot />
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
statusMessage && (
|
|
51
|
-
<div class="pui-form__status" aria-live="polite">
|
|
52
|
-
{statusMessage}
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
<div class="pui-form__actions">
|
|
58
|
-
<slot name="actions">
|
|
59
|
-
<button
|
|
60
|
-
class="pui-btn"
|
|
61
|
-
data-variant="primary"
|
|
62
|
-
type="submit"
|
|
63
|
-
disabled={busy}
|
|
64
|
-
>
|
|
65
|
-
{submitLabel}
|
|
66
|
-
</button>
|
|
67
|
-
</slot>
|
|
68
|
-
</div>
|
|
69
|
-
</form>
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
/*
|
|
73
|
-
Form.astro renders chrome only — it deliberately does NOT auto-mount
|
|
74
|
-
`mountForm`. The controller takes `validators` and an async `onSubmit`
|
|
75
|
-
handler, both of which are functions that can't be passed as Astro
|
|
76
|
-
props. Consumers wire the controller themselves from a `<script>`
|
|
77
|
-
block:
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
import Form from '@pienter/ui/components/Form.astro';
|
|
81
|
-
import TextInput from '@pienter/ui/components/TextInput.astro';
|
|
82
|
-
---
|
|
83
|
-
<Form id="signup" submitLabel="Create account">
|
|
84
|
-
<TextInput label="Email" name="email" type="email" />
|
|
85
|
-
</Form>
|
|
86
|
-
<script>
|
|
87
|
-
import { mountForm } from '@pienter/ui/utils/controllers/form.js';
|
|
88
|
-
import { isRequired, isEmail } from '@pienter/ui/utils/validation/rules.js';
|
|
89
|
-
const el = document.getElementById('signup');
|
|
90
|
-
if (el instanceof HTMLFormElement) {
|
|
91
|
-
mountForm(el, {
|
|
92
|
-
validators: { email: [isRequired(), isEmail()] },
|
|
93
|
-
onSubmit: async (data) => {
|
|
94
|
-
const res = await fetch('/api/signup', { method: 'POST', body: data });
|
|
95
|
-
if (res.ok) return { ok: true, statusMessage: 'Account created' };
|
|
96
|
-
const body = await res.json();
|
|
97
|
-
return { ok: false, fieldErrors: body.fieldErrors, errors: body.errors };
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
</script>
|
|
102
|
-
|
|
103
|
-
This explicit mount pattern is the locked Astro Form contract per the
|
|
104
|
-
Form audit entry in CONVENTIONS.md.
|
|
105
|
-
*/
|
|
106
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './input-otp.css';
|
|
3
|
-
import { generateId } from '../../../utils/a11y/id.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
id?: string;
|
|
7
|
-
label: string;
|
|
8
|
-
name?: string;
|
|
9
|
-
/**
|
|
10
|
-
* Initial OTP string. Length matches `length` (Astro pads with empty
|
|
11
|
-
* characters or trims as needed before rendering slots).
|
|
12
|
-
*/
|
|
13
|
-
value?: string;
|
|
14
|
-
/** Number of slots (default 6). */
|
|
15
|
-
length?: number;
|
|
16
|
-
/**
|
|
17
|
-
* Per-character validation pattern as a string (regex source). Default
|
|
18
|
-
* is digits-only `^[0-9]$`. Pass an alphanumeric pattern as a regex
|
|
19
|
-
* source string when the OTP code includes letters. The string is
|
|
20
|
-
* stored on a `data-pattern` attribute so the controller can rehydrate
|
|
21
|
-
* the regex post-mount.
|
|
22
|
-
*/
|
|
23
|
-
pattern?: string;
|
|
24
|
-
disabled?: boolean;
|
|
25
|
-
hint?: string;
|
|
26
|
-
errors?: string[];
|
|
27
|
-
status?: 'error' | 'success';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const {
|
|
31
|
-
id,
|
|
32
|
-
label,
|
|
33
|
-
name,
|
|
34
|
-
value = '',
|
|
35
|
-
length = 6,
|
|
36
|
-
pattern,
|
|
37
|
-
disabled = false,
|
|
38
|
-
hint,
|
|
39
|
-
errors = [],
|
|
40
|
-
status,
|
|
41
|
-
...rest
|
|
42
|
-
} = Astro.props;
|
|
43
|
-
|
|
44
|
-
const firstSlotId = id ?? generateId('input');
|
|
45
|
-
const hintId = `${firstSlotId}-hint`;
|
|
46
|
-
const errorsId = `${firstSlotId}-errors`;
|
|
47
|
-
|
|
48
|
-
const hasErrors = errors.length > 0;
|
|
49
|
-
const computedStatus = status ?? (hasErrors ? 'error' : undefined);
|
|
50
|
-
|
|
51
|
-
const describedByIds: string[] = [];
|
|
52
|
-
if (hint) describedByIds.push(hintId);
|
|
53
|
-
if (hasErrors) describedByIds.push(errorsId);
|
|
54
|
-
const describedBy =
|
|
55
|
-
describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
|
|
56
|
-
|
|
57
|
-
// Pad / trim seed value to the slot count.
|
|
58
|
-
const slots = Array.from({ length }, (_, i) => value[i] ?? '');
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
<div class="pui-field" data-status={computedStatus}>
|
|
62
|
-
<label class="pui-field__label" for={firstSlotId}>
|
|
63
|
-
{label}
|
|
64
|
-
</label>
|
|
65
|
-
<div
|
|
66
|
-
class="pui-input-otp"
|
|
67
|
-
data-pui-input-otp
|
|
68
|
-
data-disabled={disabled ? 'true' : undefined}
|
|
69
|
-
data-pattern={pattern}
|
|
70
|
-
role="group"
|
|
71
|
-
>
|
|
72
|
-
{
|
|
73
|
-
slots.map((char, i) => (
|
|
74
|
-
<input
|
|
75
|
-
class="pui-input-otp__slot pui-input"
|
|
76
|
-
type="text"
|
|
77
|
-
inputmode="numeric"
|
|
78
|
-
autocomplete="one-time-code"
|
|
79
|
-
maxlength="1"
|
|
80
|
-
id={i === 0 ? firstSlotId : `${firstSlotId}-slot-${i + 1}`}
|
|
81
|
-
aria-label={`Digit ${i + 1} of ${length}`}
|
|
82
|
-
value={char}
|
|
83
|
-
disabled={disabled}
|
|
84
|
-
aria-invalid={i === 0 && hasErrors ? 'true' : undefined}
|
|
85
|
-
aria-describedby={i === 0 ? describedBy : undefined}
|
|
86
|
-
{...(i === 0 ? rest : {})}
|
|
87
|
-
/>
|
|
88
|
-
))
|
|
89
|
-
}
|
|
90
|
-
{
|
|
91
|
-
name && (
|
|
92
|
-
<input
|
|
93
|
-
class="pui-input-otp__hidden"
|
|
94
|
-
type="hidden"
|
|
95
|
-
name={name}
|
|
96
|
-
value={value}
|
|
97
|
-
/>
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
</div>
|
|
101
|
-
{
|
|
102
|
-
hint && (
|
|
103
|
-
<p id={hintId} class="pui-field__hint">
|
|
104
|
-
{hint}
|
|
105
|
-
</p>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
{
|
|
109
|
-
hasErrors && (
|
|
110
|
-
<ul id={errorsId} class="pui-field__hint" role="alert">
|
|
111
|
-
{errors.map((error) => (
|
|
112
|
-
<li>{error}</li>
|
|
113
|
-
))}
|
|
114
|
-
</ul>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<script>
|
|
120
|
-
import { mountInputOTP } from '@pienter/ui/utils/controllers/otp.js';
|
|
121
|
-
|
|
122
|
-
// Hoisted module script — Astro de-dupes across instances.
|
|
123
|
-
function mountAll(): void {
|
|
124
|
-
const wrappers = document.querySelectorAll<HTMLElement>(
|
|
125
|
-
'[data-pui-input-otp]',
|
|
126
|
-
);
|
|
127
|
-
wrappers.forEach((wrapper) => {
|
|
128
|
-
if (wrapper.dataset.vInputOtpMounted === 'true') return;
|
|
129
|
-
let pattern: RegExp | undefined;
|
|
130
|
-
if (wrapper.dataset.pattern) {
|
|
131
|
-
try {
|
|
132
|
-
pattern = new RegExp(wrapper.dataset.pattern);
|
|
133
|
-
} catch {
|
|
134
|
-
pattern = undefined;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
mountInputOTP(wrapper, { pattern });
|
|
138
|
-
wrapper.dataset.vInputOtpMounted = 'true';
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (document.readyState === 'loading') {
|
|
143
|
-
document.addEventListener('DOMContentLoaded', mountAll, { once: true });
|
|
144
|
-
} else {
|
|
145
|
-
mountAll();
|
|
146
|
-
}
|
|
147
|
-
</script>
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './number-field.css';
|
|
3
|
-
import { generateId } from '../../../utils/a11y/id.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
id?: string;
|
|
7
|
-
label: string;
|
|
8
|
-
name?: string;
|
|
9
|
-
/**
|
|
10
|
-
* Initial numeric value. Astro renders the value once at SSR time;
|
|
11
|
-
* dynamic updates are handled by the consumer's own scripts (see
|
|
12
|
-
* the consumer-wires-their-own-script pattern in CONVENTIONS) or
|
|
13
|
-
* by the increment/decrement buttons via `mountNumberField`.
|
|
14
|
-
*/
|
|
15
|
-
value?: number | string;
|
|
16
|
-
min?: number;
|
|
17
|
-
max?: number;
|
|
18
|
-
step?: number | string;
|
|
19
|
-
placeholder?: string;
|
|
20
|
-
required?: boolean;
|
|
21
|
-
disabled?: boolean;
|
|
22
|
-
hint?: string;
|
|
23
|
-
errors?: string[];
|
|
24
|
-
status?: 'error' | 'success';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const {
|
|
28
|
-
id,
|
|
29
|
-
label,
|
|
30
|
-
name,
|
|
31
|
-
value,
|
|
32
|
-
min,
|
|
33
|
-
max,
|
|
34
|
-
step,
|
|
35
|
-
placeholder,
|
|
36
|
-
required = false,
|
|
37
|
-
disabled = false,
|
|
38
|
-
hint,
|
|
39
|
-
errors = [],
|
|
40
|
-
status,
|
|
41
|
-
...rest
|
|
42
|
-
} = Astro.props;
|
|
43
|
-
|
|
44
|
-
const inputId = id ?? generateId('input');
|
|
45
|
-
const hintId = `${inputId}-hint`;
|
|
46
|
-
const errorsId = `${inputId}-errors`;
|
|
47
|
-
|
|
48
|
-
const hasErrors = errors.length > 0;
|
|
49
|
-
const computedStatus = status ?? (hasErrors ? 'error' : undefined);
|
|
50
|
-
|
|
51
|
-
const describedByIds: string[] = [];
|
|
52
|
-
if (hint) describedByIds.push(hintId);
|
|
53
|
-
if (hasErrors) describedByIds.push(errorsId);
|
|
54
|
-
const describedBy =
|
|
55
|
-
describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
<div class="pui-field" data-status={computedStatus}>
|
|
59
|
-
<label class="pui-field__label" for={inputId}>
|
|
60
|
-
{label}
|
|
61
|
-
</label>
|
|
62
|
-
<div class="pui-number-field" data-pui-number-field>
|
|
63
|
-
<input
|
|
64
|
-
class="pui-number-field__input pui-input"
|
|
65
|
-
type="number"
|
|
66
|
-
id={inputId}
|
|
67
|
-
name={name}
|
|
68
|
-
min={min}
|
|
69
|
-
max={max}
|
|
70
|
-
step={step}
|
|
71
|
-
placeholder={placeholder}
|
|
72
|
-
required={required}
|
|
73
|
-
disabled={disabled}
|
|
74
|
-
value={value}
|
|
75
|
-
aria-invalid={hasErrors ? 'true' : undefined}
|
|
76
|
-
aria-describedby={describedBy}
|
|
77
|
-
{...rest}
|
|
78
|
-
/>
|
|
79
|
-
<div class="pui-number-field__steppers">
|
|
80
|
-
<button
|
|
81
|
-
type="button"
|
|
82
|
-
class="pui-number-field__step"
|
|
83
|
-
data-direction="increment"
|
|
84
|
-
aria-label="Increase"
|
|
85
|
-
disabled={disabled}
|
|
86
|
-
>
|
|
87
|
-
<span aria-hidden="true">+</span>
|
|
88
|
-
</button>
|
|
89
|
-
<button
|
|
90
|
-
type="button"
|
|
91
|
-
class="pui-number-field__step"
|
|
92
|
-
data-direction="decrement"
|
|
93
|
-
aria-label="Decrease"
|
|
94
|
-
disabled={disabled}
|
|
95
|
-
>
|
|
96
|
-
<span aria-hidden="true">−</span>
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
{
|
|
101
|
-
hint && (
|
|
102
|
-
<p id={hintId} class="pui-field__hint">
|
|
103
|
-
{hint}
|
|
104
|
-
</p>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
{
|
|
108
|
-
hasErrors && (
|
|
109
|
-
<ul id={errorsId} class="pui-field__hint" role="alert">
|
|
110
|
-
{errors.map((error) => (
|
|
111
|
-
<li>{error}</li>
|
|
112
|
-
))}
|
|
113
|
-
</ul>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<script>
|
|
119
|
-
import { mountNumberField } from '@pienter/ui/utils/controllers/number-field.js';
|
|
120
|
-
|
|
121
|
-
// Hoisted module script — Astro de-dupes this across every NumberField
|
|
122
|
-
// instance on the page. Mount the controller on each [data-pui-number-field]
|
|
123
|
-
// wrapper present at DOM-ready. See CONVENTIONS § hoisted-vs-inline rule.
|
|
124
|
-
function mountAll(): void {
|
|
125
|
-
const wrappers = document.querySelectorAll<HTMLElement>(
|
|
126
|
-
'[data-pui-number-field]',
|
|
127
|
-
);
|
|
128
|
-
wrappers.forEach((wrapper) => {
|
|
129
|
-
// Idempotency guard: skip wrappers that have already been mounted
|
|
130
|
-
// (e.g. on Astro view transitions / partial re-renders).
|
|
131
|
-
if (wrapper.dataset.vNumberFieldMounted === 'true') return;
|
|
132
|
-
mountNumberField(wrapper);
|
|
133
|
-
wrapper.dataset.vNumberFieldMounted = 'true';
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (document.readyState === 'loading') {
|
|
138
|
-
document.addEventListener('DOMContentLoaded', mountAll, { once: true });
|
|
139
|
-
} else {
|
|
140
|
-
mountAll();
|
|
141
|
-
}
|
|
142
|
-
</script>
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './radio-group.css';
|
|
3
|
-
import { generateId } from '../../../utils/a11y/id.js';
|
|
4
|
-
import type { RadioOption } from './types.js';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
id?: string;
|
|
8
|
-
label: string;
|
|
9
|
-
name: string;
|
|
10
|
-
options: RadioOption[];
|
|
11
|
-
value?: string;
|
|
12
|
-
disabled?: boolean;
|
|
13
|
-
orientation?: 'vertical' | 'horizontal';
|
|
14
|
-
hint?: string;
|
|
15
|
-
errors?: string[];
|
|
16
|
-
status?: 'error' | 'success';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
id,
|
|
21
|
-
label,
|
|
22
|
-
name,
|
|
23
|
-
options,
|
|
24
|
-
value,
|
|
25
|
-
disabled = false,
|
|
26
|
-
orientation = 'vertical',
|
|
27
|
-
hint,
|
|
28
|
-
errors = [],
|
|
29
|
-
status,
|
|
30
|
-
...rest
|
|
31
|
-
} = Astro.props;
|
|
32
|
-
|
|
33
|
-
const fieldId = id ?? generateId('radiogroup');
|
|
34
|
-
const hintId = `${fieldId}-hint`;
|
|
35
|
-
const errorsId = `${fieldId}-errors`;
|
|
36
|
-
|
|
37
|
-
const hasErrors = errors.length > 0;
|
|
38
|
-
const computedStatus = status ?? (hasErrors ? 'error' : undefined);
|
|
39
|
-
|
|
40
|
-
const describedByIds: string[] = [];
|
|
41
|
-
if (hint) describedByIds.push(hintId);
|
|
42
|
-
if (hasErrors) describedByIds.push(errorsId);
|
|
43
|
-
const describedBy =
|
|
44
|
-
describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
<fieldset
|
|
48
|
-
id={fieldId}
|
|
49
|
-
class="pui-field"
|
|
50
|
-
data-status={computedStatus}
|
|
51
|
-
role="radiogroup"
|
|
52
|
-
aria-describedby={describedBy}
|
|
53
|
-
aria-invalid={hasErrors ? 'true' : undefined}
|
|
54
|
-
{...rest}
|
|
55
|
-
>
|
|
56
|
-
{
|
|
57
|
-
/* aria-invalid lives on the <fieldset> (group-level signal) AND, when the
|
|
58
|
-
Astro mountForm controller is wired, on the first radio. The redundancy
|
|
59
|
-
is intentional — see CONVENTIONS.md "RadioGroup audit → ARIA
|
|
60
|
-
expectations" for the rationale. Do NOT remove either binding without
|
|
61
|
-
coordinating with the form controller. */
|
|
62
|
-
}
|
|
63
|
-
<legend class="pui-field__label">{label}</legend>
|
|
64
|
-
{
|
|
65
|
-
hint && (
|
|
66
|
-
<p id={hintId} class="pui-field__hint">
|
|
67
|
-
{hint}
|
|
68
|
-
</p>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
<div class="pui-radio-group" data-orientation={orientation}>
|
|
72
|
-
{
|
|
73
|
-
options.map((option) => {
|
|
74
|
-
const isChecked = value === option.value;
|
|
75
|
-
const isDisabled = option.disabled || disabled;
|
|
76
|
-
return (
|
|
77
|
-
<label
|
|
78
|
-
class="pui-radio-group__item"
|
|
79
|
-
data-disabled={isDisabled ? 'true' : undefined}
|
|
80
|
-
>
|
|
81
|
-
<input
|
|
82
|
-
class="pui-radio"
|
|
83
|
-
type="radio"
|
|
84
|
-
name={name}
|
|
85
|
-
value={option.value}
|
|
86
|
-
checked={isChecked}
|
|
87
|
-
disabled={isDisabled}
|
|
88
|
-
aria-checked={isChecked ? 'true' : 'false'}
|
|
89
|
-
/>
|
|
90
|
-
<span class="pui-radio-group__label">{option.label}</span>
|
|
91
|
-
</label>
|
|
92
|
-
);
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
</div>
|
|
96
|
-
{
|
|
97
|
-
hasErrors && (
|
|
98
|
-
<ul id={errorsId} class="pui-field__hint" role="alert">
|
|
99
|
-
{errors.map((error) => (
|
|
100
|
-
<li>{error}</li>
|
|
101
|
-
))}
|
|
102
|
-
</ul>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
</fieldset>
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './select.css';
|
|
3
|
-
import Icon from '../../display/icon/Icon.astro';
|
|
4
|
-
import { generateId } from '../../../utils/a11y/id.js';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
id?: string;
|
|
8
|
-
label?: string;
|
|
9
|
-
name?: string;
|
|
10
|
-
value?: string;
|
|
11
|
-
disabled?: boolean;
|
|
12
|
-
options: { value: string; label: string }[];
|
|
13
|
-
placeholder?: string;
|
|
14
|
-
hint?: string;
|
|
15
|
-
errors?: string[];
|
|
16
|
-
status?: 'error' | 'success';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
id,
|
|
21
|
-
label,
|
|
22
|
-
name,
|
|
23
|
-
value,
|
|
24
|
-
disabled = false,
|
|
25
|
-
options,
|
|
26
|
-
placeholder,
|
|
27
|
-
hint,
|
|
28
|
-
errors = [],
|
|
29
|
-
status,
|
|
30
|
-
...rest
|
|
31
|
-
} = Astro.props;
|
|
32
|
-
|
|
33
|
-
const inputId = id ?? generateId('select');
|
|
34
|
-
const hintId = `${inputId}-hint`;
|
|
35
|
-
const errorsId = `${inputId}-errors`;
|
|
36
|
-
|
|
37
|
-
const hasErrors = errors.length > 0;
|
|
38
|
-
const computedStatus = status ?? (hasErrors ? 'error' : undefined);
|
|
39
|
-
|
|
40
|
-
const describedByIds: string[] = [];
|
|
41
|
-
if (hint) describedByIds.push(hintId);
|
|
42
|
-
if (hasErrors) describedByIds.push(errorsId);
|
|
43
|
-
const describedBy =
|
|
44
|
-
describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
<div class="pui-field" data-status={computedStatus}>
|
|
48
|
-
{
|
|
49
|
-
label && (
|
|
50
|
-
<label class="pui-field__label" for={inputId}>
|
|
51
|
-
{label}
|
|
52
|
-
</label>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
<div class="pui-select">
|
|
56
|
-
<select
|
|
57
|
-
class="pui-select__control"
|
|
58
|
-
id={inputId}
|
|
59
|
-
name={name}
|
|
60
|
-
disabled={disabled}
|
|
61
|
-
aria-invalid={hasErrors ? 'true' : undefined}
|
|
62
|
-
aria-describedby={describedBy}
|
|
63
|
-
{...rest}
|
|
64
|
-
>
|
|
65
|
-
{
|
|
66
|
-
placeholder && (
|
|
67
|
-
<option
|
|
68
|
-
value=""
|
|
69
|
-
disabled
|
|
70
|
-
hidden
|
|
71
|
-
selected={value === undefined || value === ''}
|
|
72
|
-
>
|
|
73
|
-
{placeholder}
|
|
74
|
-
</option>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
{
|
|
78
|
-
options.map((opt) => (
|
|
79
|
-
<option value={opt.value} selected={value === opt.value}>
|
|
80
|
-
{opt.label}
|
|
81
|
-
</option>
|
|
82
|
-
))
|
|
83
|
-
}
|
|
84
|
-
</select>
|
|
85
|
-
<span class="pui-select__caret" aria-hidden="true">
|
|
86
|
-
<Icon name="chevron-down" size="sm" />
|
|
87
|
-
</span>
|
|
88
|
-
</div>
|
|
89
|
-
{
|
|
90
|
-
hint && (
|
|
91
|
-
<p id={hintId} class="pui-field__hint">
|
|
92
|
-
{hint}
|
|
93
|
-
</p>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
{
|
|
97
|
-
hasErrors && (
|
|
98
|
-
<ul id={errorsId} class="pui-field__hint" role="alert">
|
|
99
|
-
{errors.map((error) => (
|
|
100
|
-
<li>{error}</li>
|
|
101
|
-
))}
|
|
102
|
-
</ul>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
</div>
|