@tulipfarm/sprout 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -40
- package/dist/components/accordion.d.ts +2 -2
- package/dist/components/accordion.js +9 -9
- package/dist/components/agent-fanout.d.ts +2 -2
- package/dist/components/agent-fanout.js +65 -87
- package/dist/components/alert-dialog.d.ts +6 -6
- package/dist/components/alert-dialog.js +20 -20
- package/dist/components/alert.d.ts +6 -6
- package/dist/components/alert.js +13 -13
- package/dist/components/aspect-ratio.d.ts +2 -2
- package/dist/components/aspect-ratio.js +2 -2
- package/dist/components/attachment.d.ts +17 -17
- package/dist/components/attachment.js +82 -115
- package/dist/components/avatar.d.ts +10 -10
- package/dist/components/avatar.js +14 -14
- package/dist/components/badge.d.ts +4 -4
- package/dist/components/badge.js +18 -18
- package/dist/components/board.d.ts +107 -0
- package/dist/components/board.js +640 -0
- package/dist/components/brand-icon.d.ts +6 -6
- package/dist/components/brand-icon.js +55 -63
- package/dist/components/breadcrumb.d.ts +11 -11
- package/dist/components/breadcrumb.js +16 -18
- package/dist/components/button-group.d.ts +6 -6
- package/dist/components/button-group.js +14 -14
- package/dist/components/button.d.ts +11 -11
- package/dist/components/button.js +46 -56
- package/dist/components/calendar.d.ts +5 -5
- package/dist/components/calendar.js +44 -53
- package/dist/components/canvas.d.ts +4 -4
- package/dist/components/canvas.js +39 -46
- package/dist/components/card.d.ts +6 -6
- package/dist/components/card.js +17 -17
- package/dist/components/carousel.d.ts +7 -7
- package/dist/components/carousel.js +57 -75
- package/dist/components/chart.d.ts +5 -112
- package/dist/components/chart.js +5 -172
- package/dist/components/chat-confirmation.d.ts +13 -11
- package/dist/components/chat-confirmation.js +32 -38
- package/dist/components/chat-input.d.ts +4 -4
- package/dist/components/chat-input.js +74 -81
- package/dist/components/chat-plan.d.ts +2 -2
- package/dist/components/chat-plan.js +15 -20
- package/dist/components/chat-sources.d.ts +8 -8
- package/dist/components/chat-sources.js +18 -27
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/checkbox.js +9 -9
- package/dist/components/choice-list.d.ts +2 -2
- package/dist/components/choice-list.js +16 -18
- package/dist/components/code-block.d.ts +5 -5
- package/dist/components/code-block.js +26 -26
- package/dist/components/collapsible.d.ts +1 -1
- package/dist/components/collapsible.js +7 -11
- package/dist/components/combobox.d.ts +3 -3
- package/dist/components/combobox.js +24 -24
- package/dist/components/command.d.ts +13 -13
- package/dist/components/command.js +61 -73
- package/dist/components/context-menu.d.ts +5 -5
- package/dist/components/context-menu.js +22 -22
- package/dist/components/dialog.d.ts +6 -6
- package/dist/components/dialog.js +23 -28
- package/dist/components/direction.d.ts +1 -1
- package/dist/components/direction.js +1 -1
- package/dist/components/dropdown-menu.d.ts +5 -5
- package/dist/components/dropdown-menu.js +20 -20
- package/dist/components/field.d.ts +11 -11
- package/dist/components/field.js +41 -61
- package/dist/components/file-explorer.d.ts +2 -2
- package/dist/components/file-explorer.js +89 -105
- package/dist/components/file-viewer.d.ts +10 -10
- package/dist/components/file-viewer.js +112 -140
- package/dist/components/form.d.ts +7 -7
- package/dist/components/form.js +24 -28
- package/dist/components/glass-banner.d.ts +3 -3
- package/dist/components/glass-banner.js +21 -24
- package/dist/components/gradient-banner.d.ts +5 -5
- package/dist/components/gradient-banner.js +13 -22
- package/dist/components/hover-card.d.ts +4 -4
- package/dist/components/hover-card.js +6 -6
- package/dist/components/input-group.d.ts +11 -11
- package/dist/components/input-group.js +22 -36
- package/dist/components/input-otp.d.ts +10 -10
- package/dist/components/input-otp.js +13 -15
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +7 -7
- package/dist/components/kbd.d.ts +3 -3
- package/dist/components/kbd.js +5 -5
- package/dist/components/label.d.ts +2 -2
- package/dist/components/label.js +2 -2
- package/dist/components/mention-list.d.ts +3 -3
- package/dist/components/mention-list.js +10 -10
- package/dist/components/menubar.d.ts +6 -6
- package/dist/components/menubar.js +22 -22
- package/dist/components/message-actions.d.ts +2 -2
- package/dist/components/message-actions.js +29 -31
- package/dist/components/message-response.d.ts +3 -3
- package/dist/components/message-response.js +18 -18
- package/dist/components/message-scroller.d.ts +12 -12
- package/dist/components/message-scroller.js +44 -56
- package/dist/components/message.d.ts +11 -11
- package/dist/components/message.js +25 -25
- package/dist/components/navigation-menu.d.ts +2 -2
- package/dist/components/navigation-menu.js +15 -15
- package/dist/components/notice-banner.d.ts +2 -2
- package/dist/components/notice-banner.js +2 -2
- package/dist/components/page-content.d.ts +2 -2
- package/dist/components/page-content.js +2 -2
- package/dist/components/page-header.d.ts +1 -1
- package/dist/components/page-header.js +16 -16
- package/dist/components/pagination.d.ts +9 -9
- package/dist/components/pagination.js +17 -17
- package/dist/components/plan-outline.d.ts +2 -2
- package/dist/components/plan-outline.js +43 -60
- package/dist/components/popover.d.ts +6 -6
- package/dist/components/popover.js +15 -15
- package/dist/components/presence-cursor.d.ts +2 -2
- package/dist/components/presence-cursor.js +3 -3
- package/dist/components/progress.d.ts +1 -1
- package/dist/components/progress.js +8 -8
- package/dist/components/prompt-list.d.ts +3 -3
- package/dist/components/prompt-list.js +6 -6
- package/dist/components/questionnaire.d.ts +19 -19
- package/dist/components/questionnaire.js +183 -242
- package/dist/components/radio-group.d.ts +2 -2
- package/dist/components/radio-group.js +6 -6
- package/dist/components/reasoning-effort.d.ts +3 -3
- package/dist/components/reasoning-effort.js +9 -12
- package/dist/components/record-list.d.ts +66 -0
- package/dist/components/record-list.js +151 -0
- package/dist/components/resizable.d.ts +5 -5
- package/dist/components/resizable.js +76 -102
- package/dist/components/result-card.d.ts +10 -5
- package/dist/components/result-card.js +42 -53
- package/dist/components/result-notice.d.ts +18 -0
- package/dist/components/result-notice.js +29 -0
- package/dist/components/review-card.d.ts +6 -6
- package/dist/components/review-card.js +18 -18
- package/dist/components/rich-text-editor.d.ts +2 -2
- package/dist/components/rich-text-editor.js +56 -72
- package/dist/components/schema-diagram.d.ts +3 -3
- package/dist/components/schema-diagram.js +39 -53
- package/dist/components/scroll-area.d.ts +1 -1
- package/dist/components/scroll-area.js +10 -14
- package/dist/components/search-dialog.d.ts +6 -6
- package/dist/components/search-dialog.js +16 -16
- package/dist/components/search-field.d.ts +1 -1
- package/dist/components/search-field.js +11 -11
- package/dist/components/select.d.ts +3 -3
- package/dist/components/select.js +20 -20
- package/dist/components/selection-slot.d.ts +2 -2
- package/dist/components/selection-slot.js +6 -9
- package/dist/components/separator.d.ts +15 -15
- package/dist/components/separator.js +22 -27
- package/dist/components/settings-list.d.ts +3 -3
- package/dist/components/settings-list.js +5 -5
- package/dist/components/sheet.d.ts +6 -6
- package/dist/components/sheet.js +27 -27
- package/dist/components/sidebar.d.ts +22 -22
- package/dist/components/sidebar.js +57 -58
- package/dist/components/skeleton.d.ts +5 -5
- package/dist/components/skeleton.js +14 -21
- package/dist/components/slider.d.ts +2 -2
- package/dist/components/slider.js +6 -6
- package/dist/components/spinner.d.ts +5 -5
- package/dist/components/spinner.js +22 -26
- package/dist/components/sql-viewer.d.ts +14 -14
- package/dist/components/sql-viewer.js +99 -128
- package/dist/components/state-panel-samples.d.ts +2 -2
- package/dist/components/state-panel-samples.js +209 -245
- package/dist/components/state-panel.d.ts +2 -35
- package/dist/components/state-panel.js +2 -29
- package/dist/components/state-sample.d.ts +7 -7
- package/dist/components/state-sample.js +18 -31
- package/dist/components/switch.d.ts +1 -1
- package/dist/components/switch.js +7 -7
- package/dist/components/table-nuqs.d.ts +2 -2
- package/dist/components/table-nuqs.js +11 -12
- package/dist/components/table.d.ts +4 -4
- package/dist/components/table.js +166 -235
- package/dist/components/tabs.d.ts +2 -2
- package/dist/components/tabs.js +12 -12
- package/dist/components/textarea.d.ts +2 -2
- package/dist/components/textarea.js +6 -8
- package/dist/components/timeline.d.ts +3 -0
- package/dist/components/timeline.js +3 -0
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/toast.js +19 -25
- package/dist/components/toggle-group.d.ts +2 -2
- package/dist/components/toggle-group.js +18 -20
- package/dist/components/toggle.d.ts +2 -2
- package/dist/components/toggle.js +8 -8
- package/dist/components/tool-activity.d.ts +1 -1
- package/dist/components/tool-activity.js +83 -110
- package/dist/components/toolbar-status.d.ts +2 -2
- package/dist/components/toolbar-status.js +7 -7
- package/dist/components/tooltip.d.ts +5 -5
- package/dist/components/tooltip.js +12 -11
- package/dist/components/typed-text.d.ts +2 -2
- package/dist/components/typed-text.js +10 -12
- package/dist/components/view-switcher.d.ts +51 -0
- package/dist/components/view-switcher.js +114 -0
- package/dist/index.d.ts +98 -93
- package/dist/index.js +98 -93
- package/dist/internal/chart-kit.d.ts +142 -0
- package/dist/internal/chart-kit.js +200 -0
- package/dist/internal/charts/bars.d.ts +61 -0
- package/dist/internal/charts/bars.js +86 -0
- package/dist/internal/charts/lines.d.ts +67 -0
- package/dist/internal/charts/lines.js +70 -0
- package/dist/internal/charts/more.d.ts +77 -0
- package/dist/internal/charts/more.js +68 -0
- package/dist/internal/charts/parts.d.ts +54 -0
- package/dist/internal/charts/parts.js +82 -0
- package/dist/internal/charts/timeline-dates.d.ts +38 -0
- package/dist/internal/charts/timeline-dates.js +113 -0
- package/dist/internal/charts/timeline-window.d.ts +31 -0
- package/dist/internal/charts/timeline-window.js +26 -0
- package/dist/internal/charts/timeline.d.ts +72 -0
- package/dist/internal/charts/timeline.js +287 -0
- package/dist/internal/classes.d.ts +1 -1
- package/dist/internal/classes.js +7 -9
- package/dist/internal/code-tokens.d.ts +2 -2
- package/dist/internal/code-tokens.js +12 -12
- package/dist/internal/fieldset.d.ts +1 -1
- package/dist/internal/fieldset.js +6 -6
- package/dist/internal/file-kind.d.ts +1 -1
- package/dist/internal/file-kind.js +43 -51
- package/dist/internal/flow.d.ts +4 -4
- package/dist/internal/flow.js +20 -23
- package/dist/internal/languages.js +999 -999
- package/dist/internal/message-scroller.d.ts +3 -3
- package/dist/internal/message-scroller.js +64 -112
- package/dist/internal/pressable.d.ts +1 -1
- package/dist/internal/pressable.js +7 -7
- package/dist/internal/state-panel.d.ts +35 -0
- package/dist/internal/state-panel.js +22 -0
- package/dist/internal/syntax.d.ts +2 -2
- package/dist/internal/syntax.js +36 -47
- package/dist/styles/accordion.css +3 -5
- package/dist/styles/agent-fanout.css +64 -60
- package/dist/styles/alert-dialog.css +12 -26
- package/dist/styles/alert.css +10 -15
- package/dist/styles/attachment.css +51 -93
- package/dist/styles/avatar.css +17 -24
- package/dist/styles/badge.css +11 -36
- package/dist/styles/board.css +571 -0
- package/dist/styles/brand-icon.css +7 -8
- package/dist/styles/breadcrumb.css +4 -7
- package/dist/styles/button-group.css +8 -14
- package/dist/styles/button.css +22 -27
- package/dist/styles/calendar.css +24 -36
- package/dist/styles/canvas.css +2 -3
- package/dist/styles/card.css +7 -23
- package/dist/styles/carousel.css +11 -12
- package/dist/styles/chart.css +215 -31
- package/dist/styles/chat-input.css +15 -39
- package/dist/styles/chat-plan.css +3 -4
- package/dist/styles/chat-sources.css +3 -6
- package/dist/styles/checkbox.css +11 -25
- package/dist/styles/choice-list.css +17 -31
- package/dist/styles/code-block.css +11 -25
- package/dist/styles/collapsible.css +10 -15
- package/dist/styles/combobox.css +11 -15
- package/dist/styles/command.css +5 -8
- package/dist/styles/context-menu.css +2 -2
- package/dist/styles/dialog.css +5 -16
- package/dist/styles/dropdown-menu.css +2 -2
- package/dist/styles/field.css +15 -21
- package/dist/styles/file-explorer.css +19 -33
- package/dist/styles/file-viewer.css +13 -17
- package/dist/styles/form.css +3 -3
- package/dist/styles/glass-banner.css +12 -47
- package/dist/styles/gradient-banner.css +17 -35
- package/dist/styles/hover-card.css +9 -15
- package/dist/styles/index.css +116 -108
- package/dist/styles/input-group.css +25 -41
- package/dist/styles/input-otp.css +5 -6
- package/dist/styles/input.css +5 -5
- package/dist/styles/kbd.css +1 -1
- package/dist/styles/label.css +9 -30
- package/dist/styles/mention-list.css +6 -8
- package/dist/styles/menubar.css +5 -6
- package/dist/styles/message-actions.css +1 -1
- package/dist/styles/message-response.css +3 -5
- package/dist/styles/message-scroller.css +14 -21
- package/dist/styles/message.css +23 -60
- package/dist/styles/navigation-menu.css +14 -29
- package/dist/styles/notice-banner.css +1 -2
- package/dist/styles/page-header.css +7 -10
- package/dist/styles/pagination.css +1 -4
- package/dist/styles/plan-outline.css +20 -37
- package/dist/styles/popover.css +10 -19
- package/dist/styles/presence-cursor.css +1 -2
- package/dist/styles/progress.css +2 -4
- package/dist/styles/prompt-list.css +6 -17
- package/dist/styles/questionnaire.css +16 -40
- package/dist/styles/radio-group.css +12 -23
- package/dist/styles/reasoning-effort.css +8 -15
- package/dist/styles/record-list.css +243 -0
- package/dist/styles/resizable.css +10 -11
- package/dist/styles/result-card.css +17 -3
- package/dist/styles/result-notice.css +80 -0
- package/dist/styles/review-card.css +2 -3
- package/dist/styles/rich-text-editor.css +1 -2
- package/dist/styles/schema-diagram.css +12 -17
- package/dist/styles/scroll-area.css +8 -10
- package/dist/styles/search-dialog.css +1 -5
- package/dist/styles/search-field.css +1 -2
- package/dist/styles/select.css +7 -7
- package/dist/styles/selection-slot.css +1 -2
- package/dist/styles/separator.css +7 -8
- package/dist/styles/settings-list.css +1 -2
- package/dist/styles/sheet.css +40 -73
- package/dist/styles/sidebar.css +17 -21
- package/dist/styles/skeleton.css +9 -11
- package/dist/styles/slider.css +11 -21
- package/dist/styles/spinner.css +2 -2
- package/dist/styles/sql-viewer.css +29 -29
- package/dist/styles/state-panel.css +8 -9
- package/dist/styles/state-sample.css +20 -33
- package/dist/styles/switch.css +11 -18
- package/dist/styles/table.css +51 -77
- package/dist/styles/tabs.css +12 -16
- package/dist/styles/textarea.css +3 -3
- package/dist/styles/themes/chatgpt.css +11 -23
- package/dist/styles/themes/claude.css +12 -24
- package/dist/styles/themes/cloudflare.css +11 -23
- package/dist/styles/themes/discord.css +11 -23
- package/dist/styles/themes/github.css +11 -23
- package/dist/styles/themes/grok.css +11 -23
- package/dist/styles/themes/linear.css +12 -25
- package/dist/styles/themes/neutral.css +11 -23
- package/dist/styles/themes/notion.css +11 -23
- package/dist/styles/themes/perplexity.css +11 -23
- package/dist/styles/themes/raycast.css +11 -23
- package/dist/styles/themes/spotify.css +11 -23
- package/dist/styles/themes/stripe.css +11 -23
- package/dist/styles/themes/supabase.css +11 -23
- package/dist/styles/themes/tulipfarm.css +9 -10
- package/dist/styles/themes/vercel.css +12 -25
- package/dist/styles/timeline.css +276 -0
- package/dist/styles/toast.css +13 -20
- package/dist/styles/toggle-group.css +17 -23
- package/dist/styles/toggle.css +8 -8
- package/dist/styles/tool-activity.css +7 -13
- package/dist/styles/toolbar-status.css +2 -7
- package/dist/styles/tooltip.css +15 -21
- package/dist/styles/view-switcher.css +279 -0
- package/package.json +21 -1
|
@@ -1,57 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { mergeProps } from
|
|
4
|
-
import { useRender } from
|
|
5
|
-
import { createContext, useCallback, useContext, useId, useLayoutEffect, useMemo, useRef, useState, } from
|
|
6
|
-
import { Check } from
|
|
7
|
-
import { cn } from
|
|
8
|
-
import { buttonVariants
|
|
9
|
-
import { Kbd } from
|
|
10
|
-
export const questionnaireItemStatusNames = [
|
|
11
|
-
|
|
12
|
-
"answered",
|
|
13
|
-
"skipped",
|
|
14
|
-
];
|
|
15
|
-
export const questionnaireShortcutModeNames = ["letters", "numbers"];
|
|
3
|
+
import { mergeProps } from '@base-ui/react/merge-props';
|
|
4
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
5
|
+
import { createContext, useCallback, useContext, useId, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
6
|
+
import { Check } from 'reicon-react/icons/Check';
|
|
7
|
+
import { cn } from '../internal/classes.js';
|
|
8
|
+
import { buttonVariants } from './button.js';
|
|
9
|
+
import { Kbd } from './kbd.js';
|
|
10
|
+
export const questionnaireItemStatusNames = ['unanswered', 'answered', 'skipped'];
|
|
11
|
+
export const questionnaireShortcutModeNames = ['letters', 'numbers'];
|
|
16
12
|
function hasText(value) {
|
|
17
13
|
return Array.isArray(value)
|
|
18
|
-
? value.some(
|
|
14
|
+
? value.some(part => String(part).trim().length > 0)
|
|
19
15
|
: value != null && String(value).trim().length > 0;
|
|
20
16
|
}
|
|
21
17
|
function shortcutKeys(mode) {
|
|
22
|
-
if (mode ===
|
|
18
|
+
if (mode === 'letters')
|
|
23
19
|
return Array.from({ length: 26 }, (_, index) => String.fromCharCode(65 + index));
|
|
24
|
-
if (mode ===
|
|
20
|
+
if (mode === 'numbers')
|
|
25
21
|
return Array.from({ length: 9 }, (_, index) => String(index + 1));
|
|
26
22
|
return [];
|
|
27
23
|
}
|
|
28
24
|
function shortcutFromKey(key, mode) {
|
|
29
|
-
const normalized = mode ===
|
|
25
|
+
const normalized = mode === 'letters' ? key.toUpperCase() : key;
|
|
30
26
|
return shortcutKeys(mode).includes(normalized) ? normalized : null;
|
|
31
27
|
}
|
|
32
28
|
function keyShortcuts(shortcut, enter) {
|
|
33
|
-
return
|
|
29
|
+
return [shortcut, enter ? 'Enter' : null].filter(Boolean).join(' ') || undefined;
|
|
34
30
|
}
|
|
35
31
|
function isAnswered(control) {
|
|
36
|
-
return control.type ===
|
|
32
|
+
return control.type === 'choice'
|
|
37
33
|
? control.element.checked
|
|
38
|
-
: control.element.hasAttribute(
|
|
34
|
+
: control.element.hasAttribute('name') && hasText(control.element.value);
|
|
39
35
|
}
|
|
40
36
|
function isEmptyTextInput(control) {
|
|
41
|
-
return (control?.type ===
|
|
42
|
-
[
|
|
37
|
+
return (control?.type === 'input' &&
|
|
38
|
+
['email', 'password', 'search', 'tel', 'text', 'url'].includes(control.element.type) &&
|
|
43
39
|
!hasText(control.element.value));
|
|
44
40
|
}
|
|
45
41
|
function isTextEntry(element) {
|
|
46
|
-
if (element instanceof HTMLTextAreaElement ||
|
|
47
|
-
element instanceof HTMLSelectElement)
|
|
42
|
+
if (element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement)
|
|
48
43
|
return true;
|
|
49
44
|
if (element instanceof HTMLInputElement)
|
|
50
|
-
return ![
|
|
45
|
+
return !['button', 'checkbox', 'radio', 'reset', 'submit'].includes(element.type);
|
|
51
46
|
return element instanceof HTMLElement && element.isContentEditable;
|
|
52
47
|
}
|
|
53
48
|
function isRadio(element) {
|
|
54
|
-
return element instanceof HTMLInputElement && element.type ===
|
|
49
|
+
return element instanceof HTMLInputElement && element.type === 'radio';
|
|
55
50
|
}
|
|
56
51
|
function byDocumentOrder(a, b) {
|
|
57
52
|
if (a.element === b.element)
|
|
@@ -64,7 +59,7 @@ function byDocumentOrder(a, b) {
|
|
|
64
59
|
return 0;
|
|
65
60
|
}
|
|
66
61
|
function assignRef(ref, value) {
|
|
67
|
-
if (typeof ref ===
|
|
62
|
+
if (typeof ref === 'function')
|
|
68
63
|
ref(value);
|
|
69
64
|
else if (ref)
|
|
70
65
|
ref.current = value;
|
|
@@ -72,12 +67,8 @@ function assignRef(ref, value) {
|
|
|
72
67
|
function initialItem(definitions, defaultItem) {
|
|
73
68
|
if (!definitions)
|
|
74
69
|
return defaultItem ?? null;
|
|
75
|
-
const match = defaultItem
|
|
76
|
-
|
|
77
|
-
: undefined;
|
|
78
|
-
return match && !match.disabled
|
|
79
|
-
? match.name
|
|
80
|
-
: (definitions.enabledItems[0]?.name ?? null);
|
|
70
|
+
const match = defaultItem ? definitions.itemByName.get(defaultItem) : undefined;
|
|
71
|
+
return match && !match.disabled ? match.name : (definitions.enabledItems[0]?.name ?? null);
|
|
81
72
|
}
|
|
82
73
|
function shortcutsForDefinition(definition, mode) {
|
|
83
74
|
const map = new Map();
|
|
@@ -120,8 +111,8 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
120
111
|
? null
|
|
121
112
|
: {
|
|
122
113
|
items,
|
|
123
|
-
enabledItems: items.filter(
|
|
124
|
-
itemByName: new Map(items.map(
|
|
114
|
+
enabledItems: items.filter(entry => !entry.disabled),
|
|
115
|
+
itemByName: new Map(items.map(entry => [entry.name, entry])),
|
|
125
116
|
}, [items]);
|
|
126
117
|
const [registered, setRegistered] = useState([]);
|
|
127
118
|
const [internalItem, setInternalItem] = useState(() => initialItem(definitions, defaultItem));
|
|
@@ -134,32 +125,24 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
134
125
|
const nativeValidation = noValidate === false;
|
|
135
126
|
const shortcutMode = shortcuts ?? null;
|
|
136
127
|
useLayoutEffect(() => {
|
|
137
|
-
if (!form || typeof MutationObserver ===
|
|
128
|
+
if (!form || typeof MutationObserver === 'undefined')
|
|
138
129
|
return;
|
|
139
|
-
const observer = new MutationObserver(() => setDomVersion(
|
|
130
|
+
const observer = new MutationObserver(() => setDomVersion(version => version + 1));
|
|
140
131
|
observer.observe(form, { childList: true, subtree: true });
|
|
141
132
|
return () => observer.disconnect();
|
|
142
133
|
}, [form]);
|
|
143
|
-
const enabledRegistered = useMemo(() => registered.filter(
|
|
134
|
+
const enabledRegistered = useMemo(() => registered.filter(entry => !entry.disabled).sort(byDocumentOrder),
|
|
144
135
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
145
136
|
[registered, domVersion]);
|
|
146
|
-
const registeredByName = useMemo(() => new Map(enabledRegistered.map(
|
|
137
|
+
const registeredByName = useMemo(() => new Map(enabledRegistered.map(entry => [entry.name, entry])), [enabledRegistered]);
|
|
147
138
|
const sequence = definitions?.enabledItems ?? enabledRegistered;
|
|
148
|
-
const index = sequence.findIndex(
|
|
149
|
-
const active = index < 0 || !activeName
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
? definitions?.itemByName.get(activeName)
|
|
154
|
-
: undefined;
|
|
155
|
-
const activeRequired = index < 0
|
|
156
|
-
? null
|
|
157
|
-
: activeDefinition
|
|
158
|
-
? !!activeDefinition.required
|
|
159
|
-
: (active?.required ?? false);
|
|
160
|
-
const activeStatus = index < 0 ? null : (active?.status ?? (activeName ? "unanswered" : null));
|
|
139
|
+
const index = sequence.findIndex(entry => entry.name === activeName);
|
|
140
|
+
const active = index < 0 || !activeName ? null : (registeredByName.get(activeName) ?? null);
|
|
141
|
+
const activeDefinition = activeName ? definitions?.itemByName.get(activeName) : undefined;
|
|
142
|
+
const activeRequired = index < 0 ? null : activeDefinition ? !!activeDefinition.required : (active?.required ?? false);
|
|
143
|
+
const activeStatus = index < 0 ? null : (active?.status ?? (activeName ? 'unanswered' : null));
|
|
161
144
|
const ordered = useMemo(() => definitions
|
|
162
|
-
? definitions.enabledItems.flatMap(
|
|
145
|
+
? definitions.enabledItems.flatMap(entry => {
|
|
163
146
|
const match = registeredByName.get(entry.name);
|
|
164
147
|
return match ? [match] : [];
|
|
165
148
|
})
|
|
@@ -168,7 +151,7 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
168
151
|
const current = index < 0 ? 0 : index + 1;
|
|
169
152
|
const first = total > 0 && index === 0;
|
|
170
153
|
const last = total > 0 && index === total - 1;
|
|
171
|
-
const go = useCallback((name, target =
|
|
154
|
+
const go = useCallback((name, target = 'item') => {
|
|
172
155
|
if (name === activeName)
|
|
173
156
|
return;
|
|
174
157
|
pending.current = { name, target };
|
|
@@ -197,18 +180,18 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
197
180
|
}
|
|
198
181
|
return;
|
|
199
182
|
}
|
|
200
|
-
if (request.target ===
|
|
183
|
+
if (request.target === 'invalid')
|
|
201
184
|
active?.focusInvalid();
|
|
202
185
|
else
|
|
203
186
|
active?.focus();
|
|
204
187
|
pending.current = null;
|
|
205
188
|
}, [active, activeName, controlled, index, sequence, go, total]);
|
|
206
189
|
const registerItem = useCallback((entry) => {
|
|
207
|
-
setRegistered(
|
|
208
|
-
...list.filter(
|
|
190
|
+
setRegistered(list => [
|
|
191
|
+
...list.filter(other => other.element !== entry.element && other.name !== entry.name),
|
|
209
192
|
entry,
|
|
210
193
|
]);
|
|
211
|
-
return () => setRegistered(
|
|
194
|
+
return () => setRegistered(list => list.filter(other => other !== entry));
|
|
212
195
|
}, []);
|
|
213
196
|
const goPrevious = useCallback(() => {
|
|
214
197
|
if (index <= 0)
|
|
@@ -255,16 +238,15 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
255
238
|
entry.reset();
|
|
256
239
|
const target = definitions
|
|
257
240
|
? initialItem(definitions, defaultItem)
|
|
258
|
-
: (enabledRegistered.find(
|
|
259
|
-
enabledRegistered[0]?.name);
|
|
241
|
+
: (enabledRegistered.find(entry => entry.name === defaultItem)?.name ?? enabledRegistered[0]?.name);
|
|
260
242
|
if (target)
|
|
261
243
|
go(target);
|
|
262
244
|
}
|
|
263
245
|
function handleSubmit(event) {
|
|
264
|
-
const invalid = ordered.find(
|
|
246
|
+
const invalid = ordered.find(entry => !entry.validate());
|
|
265
247
|
if (invalid) {
|
|
266
248
|
event.preventDefault();
|
|
267
|
-
go(invalid.name,
|
|
249
|
+
go(invalid.name, 'invalid');
|
|
268
250
|
if (invalid.name === activeName) {
|
|
269
251
|
invalid.focusInvalid();
|
|
270
252
|
pending.current = null;
|
|
@@ -281,10 +263,7 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
281
263
|
!active ||
|
|
282
264
|
!(target instanceof Element))
|
|
283
265
|
return;
|
|
284
|
-
if (event.key ===
|
|
285
|
-
(event.metaKey || event.ctrlKey) &&
|
|
286
|
-
!event.altKey &&
|
|
287
|
-
!event.shiftKey) {
|
|
266
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey) {
|
|
288
267
|
event.preventDefault();
|
|
289
268
|
if (!event.repeat)
|
|
290
269
|
advance();
|
|
@@ -292,24 +271,22 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
292
271
|
}
|
|
293
272
|
if (event.metaKey || event.ctrlKey || event.altKey)
|
|
294
273
|
return;
|
|
295
|
-
if ((event.key ===
|
|
296
|
-
active.moveAnswerFocus(target, event.key ===
|
|
274
|
+
if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') &&
|
|
275
|
+
active.moveAnswerFocus(target, event.key === 'ArrowDown' ? 'next' : 'previous')) {
|
|
297
276
|
event.preventDefault();
|
|
298
277
|
return;
|
|
299
278
|
}
|
|
300
|
-
if ((event.key ===
|
|
301
|
-
!isTextEntry(target) &&
|
|
302
|
-
!isRadio(target)) {
|
|
279
|
+
if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') && !isTextEntry(target) && !isRadio(target)) {
|
|
303
280
|
event.preventDefault();
|
|
304
281
|
if (event.repeat)
|
|
305
282
|
return;
|
|
306
|
-
if (event.key ===
|
|
283
|
+
if (event.key === 'ArrowLeft')
|
|
307
284
|
goPrevious();
|
|
308
|
-
else if (active.status !==
|
|
285
|
+
else if (active.status !== 'unanswered')
|
|
309
286
|
goNext();
|
|
310
287
|
return;
|
|
311
288
|
}
|
|
312
|
-
if (event.key ===
|
|
289
|
+
if (event.key === 'Enter') {
|
|
313
290
|
const control = active.getAnswerByElement(target);
|
|
314
291
|
if (!control)
|
|
315
292
|
return;
|
|
@@ -328,7 +305,7 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
328
305
|
if (event.repeat)
|
|
329
306
|
return;
|
|
330
307
|
control.element.focus();
|
|
331
|
-
if (control.type ===
|
|
308
|
+
if (control.type === 'choice')
|
|
332
309
|
control.element.click();
|
|
333
310
|
}
|
|
334
311
|
const context = useMemo(() => ({
|
|
@@ -369,11 +346,11 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
369
346
|
assignRef(ref, element);
|
|
370
347
|
}, [ref]);
|
|
371
348
|
const element = useRender({
|
|
372
|
-
defaultTagName:
|
|
349
|
+
defaultTagName: 'form',
|
|
373
350
|
props: mergeProps({
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
className: cn(
|
|
351
|
+
'data-slot': 'questionnaire',
|
|
352
|
+
'data-shortcuts': shortcutMode ?? undefined,
|
|
353
|
+
className: cn('sprout-questionnaire', className),
|
|
377
354
|
noValidate,
|
|
378
355
|
onKeyDown: handleKeyDown,
|
|
379
356
|
onReset: handleReset,
|
|
@@ -386,30 +363,30 @@ export function Questionnaire({ className, defaultItem, item, items, noValidate
|
|
|
386
363
|
}
|
|
387
364
|
/** "Question 2 of 3", announced politely as the step changes. */
|
|
388
365
|
export function QuestionnaireProgress({ children, className, render, ...props }) {
|
|
389
|
-
const { current, first, last, total } = useRoot(
|
|
366
|
+
const { current, first, last, total } = useRoot('QuestionnaireProgress');
|
|
390
367
|
const text = total ? `Question ${current} of ${total}` : undefined;
|
|
391
368
|
return useRender({
|
|
392
|
-
defaultTagName:
|
|
369
|
+
defaultTagName: 'div',
|
|
393
370
|
render,
|
|
394
371
|
props: mergeProps({
|
|
395
|
-
|
|
396
|
-
className: cn(
|
|
397
|
-
role:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
372
|
+
'data-slot': 'questionnaire-progress',
|
|
373
|
+
className: cn('sprout-questionnaire-progress', className),
|
|
374
|
+
role: 'progressbar',
|
|
375
|
+
'aria-label': 'Questionnaire progress',
|
|
376
|
+
'aria-live': 'polite',
|
|
377
|
+
'aria-valuemin': total ? 1 : undefined,
|
|
378
|
+
'aria-valuemax': total || undefined,
|
|
379
|
+
'aria-valuenow': total ? current : undefined,
|
|
380
|
+
'aria-valuetext': text,
|
|
404
381
|
children: children ?? text,
|
|
405
382
|
}, props),
|
|
406
383
|
state: { current, first, last, total },
|
|
407
384
|
});
|
|
408
385
|
}
|
|
409
386
|
/** One question. Only the current item is shown; the rest are hidden and inert. */
|
|
410
|
-
export function QuestionnaireItem({
|
|
411
|
-
const root = useRoot(
|
|
412
|
-
const { activeItemName, domVersion, first, last, itemDefinitionByName, nativeValidation, registerItem, shortcuts
|
|
387
|
+
export function QuestionnaireItem({ 'aria-describedby': ariaDescribedBy, 'aria-keyshortcuts': ariaKeyShortcuts, children, className, disabled = false, invalid = false, multiple = false, name, onStatusChange, ref, required = false, ...props }) {
|
|
388
|
+
const root = useRoot('QuestionnaireItem');
|
|
389
|
+
const { activeItemName, domVersion, first, last, itemDefinitionByName, nativeValidation, registerItem, shortcuts } = root;
|
|
413
390
|
const [element, setElement] = useState(null);
|
|
414
391
|
const [controls, setControls] = useState([]);
|
|
415
392
|
const [attempted, setAttempted] = useState(false);
|
|
@@ -426,30 +403,22 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
426
403
|
const sorted = useMemo(() => [...controls].sort(byDocumentOrder),
|
|
427
404
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
428
405
|
[controls, domVersion]);
|
|
429
|
-
const enabled = useMemo(() => sorted.filter(
|
|
430
|
-
const answered = enabled.some(
|
|
431
|
-
const status = skipped
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
? "answered"
|
|
435
|
-
: "unanswered";
|
|
436
|
-
const skippedOptional = status === "skipped" && !required;
|
|
437
|
-
const valid = disabled || skippedOptional || (!invalid && status === "answered");
|
|
406
|
+
const enabled = useMemo(() => sorted.filter(control => !control.disabled), [sorted]);
|
|
407
|
+
const answered = enabled.some(control => selected.includes(control.id));
|
|
408
|
+
const status = skipped ? 'skipped' : answered ? 'answered' : 'unanswered';
|
|
409
|
+
const skippedOptional = status === 'skipped' && !required;
|
|
410
|
+
const valid = disabled || skippedOptional || (!invalid && status === 'answered');
|
|
438
411
|
const showInvalid = !disabled && !skippedOptional && (invalid || (attempted && !valid));
|
|
439
|
-
const hasInputAnswer = enabled.some(
|
|
412
|
+
const hasInputAnswer = enabled.some(control => control.type === 'input');
|
|
440
413
|
const lastStatus = useRef(status);
|
|
441
414
|
const definition = itemDefinitionByName?.get(name);
|
|
442
|
-
const shortcutByChoiceValue = useMemo(() => itemDefinitionByName
|
|
443
|
-
? shortcutsForDefinition(definition, shortcuts)
|
|
444
|
-
: null, [definition, itemDefinitionByName, shortcuts]);
|
|
415
|
+
const shortcutByChoiceValue = useMemo(() => (itemDefinitionByName ? shortcutsForDefinition(definition, shortcuts) : null), [definition, itemDefinitionByName, shortcuts]);
|
|
445
416
|
const shortcutByAnswerId = useMemo(() => {
|
|
446
417
|
if (shortcutByChoiceValue)
|
|
447
418
|
return new Map();
|
|
448
419
|
const keys = shortcutKeys(shortcuts);
|
|
449
|
-
const choices = enabled.filter(
|
|
450
|
-
return new Map(choices
|
|
451
|
-
.slice(0, keys.length)
|
|
452
|
-
.map((control, position) => [control.id, keys[position]]));
|
|
420
|
+
const choices = enabled.filter(control => control.type === 'choice');
|
|
421
|
+
return new Map(choices.slice(0, keys.length).map((control, position) => [control.id, keys[position]]));
|
|
453
422
|
}, [enabled, shortcutByChoiceValue, shortcuts]);
|
|
454
423
|
useLayoutEffect(() => {
|
|
455
424
|
if (lastStatus.current === status)
|
|
@@ -458,16 +427,16 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
458
427
|
onStatusChange?.(status);
|
|
459
428
|
}, [onStatusChange, status]);
|
|
460
429
|
const registerAnswerControl = useCallback((control) => {
|
|
461
|
-
setControls(
|
|
462
|
-
...list.filter(
|
|
430
|
+
setControls(list => [
|
|
431
|
+
...list.filter(other => other.element !== control.element && other.id !== control.id),
|
|
463
432
|
control,
|
|
464
433
|
]);
|
|
465
|
-
return () => setControls(
|
|
434
|
+
return () => setControls(list => list.filter(other => other !== control));
|
|
466
435
|
}, []);
|
|
467
436
|
const select = useCallback((id, on) => {
|
|
468
|
-
setSelected(
|
|
437
|
+
setSelected(list => {
|
|
469
438
|
if (!on)
|
|
470
|
-
return list.filter(
|
|
439
|
+
return list.filter(other => other !== id);
|
|
471
440
|
if (!multiple)
|
|
472
441
|
return [id];
|
|
473
442
|
return list.includes(id) ? list : [...list, id];
|
|
@@ -484,19 +453,16 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
484
453
|
}, [select]);
|
|
485
454
|
const registerAnswerSelection = useCallback((id, initial) => {
|
|
486
455
|
if (initial) {
|
|
487
|
-
defaults.current = [
|
|
488
|
-
|
|
489
|
-
id,
|
|
490
|
-
];
|
|
491
|
-
setSelected((list) => {
|
|
456
|
+
defaults.current = [...defaults.current.filter(other => other !== id), id];
|
|
457
|
+
setSelected(list => {
|
|
492
458
|
if (multipleRef.current)
|
|
493
459
|
return list.includes(id) ? list : [...list, id];
|
|
494
460
|
return list.length ? list : [id];
|
|
495
461
|
});
|
|
496
462
|
}
|
|
497
463
|
return () => {
|
|
498
|
-
defaults.current = defaults.current.filter(
|
|
499
|
-
setSelected(
|
|
464
|
+
defaults.current = defaults.current.filter(other => other !== id);
|
|
465
|
+
setSelected(list => list.filter(other => other !== id));
|
|
500
466
|
};
|
|
501
467
|
}, []);
|
|
502
468
|
const setAnswerDefault = useCallback((id, on) => {
|
|
@@ -505,15 +471,15 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
505
471
|
defaults.current = [...defaults.current, id];
|
|
506
472
|
return;
|
|
507
473
|
}
|
|
508
|
-
defaults.current = defaults.current.filter(
|
|
474
|
+
defaults.current = defaults.current.filter(other => other !== id);
|
|
509
475
|
}, []);
|
|
510
476
|
const registerDescription = useCallback((id) => {
|
|
511
|
-
setDescriptionIds(
|
|
512
|
-
return () => setDescriptionIds(
|
|
477
|
+
setDescriptionIds(list => (list.includes(id) ? list : [...list, id]));
|
|
478
|
+
return () => setDescriptionIds(list => list.filter(other => other !== id));
|
|
513
479
|
}, []);
|
|
514
480
|
const registerError = useCallback((id) => {
|
|
515
|
-
setErrorIds(
|
|
516
|
-
return () => setErrorIds(
|
|
481
|
+
setErrorIds(list => (list.includes(id) ? list : [...list, id]));
|
|
482
|
+
return () => setErrorIds(list => list.filter(other => other !== id));
|
|
517
483
|
}, []);
|
|
518
484
|
const validate = useCallback(() => {
|
|
519
485
|
setAttempted(true);
|
|
@@ -521,9 +487,7 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
521
487
|
return false;
|
|
522
488
|
if (!nativeValidation)
|
|
523
489
|
return true;
|
|
524
|
-
const failing = enabled.find(
|
|
525
|
-
control.element.willValidate &&
|
|
526
|
-
!control.element.validity.valid);
|
|
490
|
+
const failing = enabled.find(control => isAnswered(control) && control.element.willValidate && !control.element.validity.valid);
|
|
527
491
|
if (!failing)
|
|
528
492
|
return true;
|
|
529
493
|
failing.element.focus();
|
|
@@ -532,15 +496,15 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
532
496
|
}, [enabled, nativeValidation, valid]);
|
|
533
497
|
const focus = useCallback(() => element?.focus(), [element]);
|
|
534
498
|
const focusInvalid = useCallback(() => {
|
|
535
|
-
const filled = element?.querySelector(
|
|
536
|
-
const firstField = element?.querySelector(
|
|
499
|
+
const filled = element?.querySelector('input[data-filled][name]:not(:disabled)');
|
|
500
|
+
const firstField = element?.querySelector('input:not([type=hidden]):not(:disabled), textarea:not(:disabled)');
|
|
537
501
|
(filled ?? firstField ?? element)?.focus();
|
|
538
502
|
}, [element]);
|
|
539
503
|
const reset = useCallback(() => {
|
|
540
504
|
setAttempted(false);
|
|
541
505
|
setSkipped(false);
|
|
542
506
|
setSelected(multiple ? [...defaults.current] : defaults.current.slice(0, 1));
|
|
543
|
-
setResetVersion(
|
|
507
|
+
setResetVersion(version => version + 1);
|
|
544
508
|
}, [multiple]);
|
|
545
509
|
const skip = useCallback(() => {
|
|
546
510
|
if (required)
|
|
@@ -553,38 +517,34 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
553
517
|
previousMultiple.current = multiple;
|
|
554
518
|
if (!wasMultiple || multiple)
|
|
555
519
|
return;
|
|
556
|
-
setSelected(
|
|
557
|
-
const keep = enabled.find(
|
|
520
|
+
setSelected(list => {
|
|
521
|
+
const keep = enabled.find(control => list.includes(control.id));
|
|
558
522
|
return keep ? [keep.id] : [];
|
|
559
523
|
});
|
|
560
524
|
}, [enabled, multiple]);
|
|
561
|
-
const getAnswerByElement = useCallback((target) => enabled.find(
|
|
525
|
+
const getAnswerByElement = useCallback((target) => enabled.find(control => control.element === target) ?? null, [enabled]);
|
|
562
526
|
const getAnswerByShortcut = useCallback((shortcut) => {
|
|
563
527
|
if (shortcutByChoiceValue) {
|
|
564
528
|
const value = [...shortcutByChoiceValue.entries()].find(([, key]) => key === shortcut)?.[0];
|
|
565
|
-
return
|
|
529
|
+
return enabled.find(control => control.type === 'choice' && control.value === value) ?? null;
|
|
566
530
|
}
|
|
567
531
|
const id = [...shortcutByAnswerId.entries()].find(([, key]) => key === shortcut)?.[0];
|
|
568
|
-
return enabled.find(
|
|
532
|
+
return enabled.find(control => control.id === id) ?? null;
|
|
569
533
|
}, [enabled, shortcutByAnswerId, shortcutByChoiceValue]);
|
|
570
534
|
const moveAnswerFocus = useCallback((target, direction) => {
|
|
571
|
-
const position = enabled.findIndex(
|
|
535
|
+
const position = enabled.findIndex(control => control.element === target);
|
|
572
536
|
const currentControl = position < 0 ? null : (enabled[position] ?? null);
|
|
573
537
|
if (!enabled.length ||
|
|
574
538
|
(isTextEntry(target) && !isEmptyTextInput(currentControl)) ||
|
|
575
539
|
(position < 0 && target !== element))
|
|
576
540
|
return false;
|
|
577
541
|
const next = position < 0
|
|
578
|
-
? (enabled.find(isAnswered) ??
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
enabled.length];
|
|
582
|
-
if (!next ||
|
|
583
|
-
next.element === target ||
|
|
584
|
-
(position >= 0 && isRadio(target) && isRadio(next.element)))
|
|
542
|
+
? (enabled.find(isAnswered) ?? (direction === 'next' ? enabled[0] : enabled[enabled.length - 1]))
|
|
543
|
+
: enabled[(position + (direction === 'next' ? 1 : -1) + enabled.length) % enabled.length];
|
|
544
|
+
if (!next || next.element === target || (position >= 0 && isRadio(target) && isRadio(next.element)))
|
|
585
545
|
return false;
|
|
586
546
|
next.element.focus();
|
|
587
|
-
if (next.type ===
|
|
547
|
+
if (next.type === 'choice' && isRadio(next.element))
|
|
588
548
|
next.element.click();
|
|
589
549
|
return true;
|
|
590
550
|
}, [element, enabled]);
|
|
@@ -597,9 +557,7 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
597
557
|
disabled,
|
|
598
558
|
required,
|
|
599
559
|
status,
|
|
600
|
-
choices: sorted.flatMap(
|
|
601
|
-
? [{ value: control.value, disabled: control.ownDisabled }]
|
|
602
|
-
: []),
|
|
560
|
+
choices: sorted.flatMap(control => control.type === 'choice' ? [{ value: control.value, disabled: control.ownDisabled }] : []),
|
|
603
561
|
focus,
|
|
604
562
|
focusInvalid,
|
|
605
563
|
validate,
|
|
@@ -669,18 +627,16 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
669
627
|
setElement(node);
|
|
670
628
|
assignRef(ref, node);
|
|
671
629
|
}, [ref]);
|
|
672
|
-
const describedBy = [...descriptionIds, ...(showInvalid ? errorIds : []), ariaDescribedBy]
|
|
673
|
-
.filter(Boolean)
|
|
674
|
-
.join(" ") || undefined;
|
|
630
|
+
const describedBy = [...descriptionIds, ...(showInvalid ? errorIds : []), ariaDescribedBy].filter(Boolean).join(' ') || undefined;
|
|
675
631
|
const itemShortcuts = [
|
|
676
632
|
ariaKeyShortcuts,
|
|
677
|
-
active ?
|
|
678
|
-
active && enabled.length ?
|
|
679
|
-
active && !first ?
|
|
680
|
-
active && !last && status !==
|
|
633
|
+
active ? 'Meta+Enter Control+Enter' : undefined,
|
|
634
|
+
active && enabled.length ? 'ArrowUp ArrowDown' : undefined,
|
|
635
|
+
active && !first ? 'ArrowLeft' : undefined,
|
|
636
|
+
active && !last && status !== 'unanswered' ? 'ArrowRight' : undefined,
|
|
681
637
|
]
|
|
682
638
|
.filter(Boolean)
|
|
683
|
-
.join(
|
|
639
|
+
.join(' ') || undefined;
|
|
684
640
|
const state = {
|
|
685
641
|
active,
|
|
686
642
|
disabled,
|
|
@@ -689,46 +645,46 @@ export function QuestionnaireItem({ "aria-describedby": ariaDescribedBy, "aria-k
|
|
|
689
645
|
required,
|
|
690
646
|
status,
|
|
691
647
|
};
|
|
692
|
-
return (_jsx(ItemContext.Provider, { value: context, children: _jsx("fieldset", { "data-slot": "questionnaire-item", "data-active": active ?
|
|
648
|
+
return (_jsx(ItemContext.Provider, { value: context, children: _jsx("fieldset", { "data-slot": "questionnaire-item", "data-active": active ? '' : undefined, "data-disabled": disabled ? '' : undefined, "data-invalid": showInvalid ? '' : undefined, "data-multiple": multiple ? '' : undefined, "data-required": required ? '' : undefined, "data-status": state.status, ...props, ref: handleRef, className: cn('sprout-questionnaire-item', className), "aria-describedby": describedBy, "aria-invalid": showInvalid || undefined, "aria-keyshortcuts": itemShortcuts, disabled: disabled, hidden: !active, inert: !active, tabIndex: -1, children: children }) }));
|
|
693
649
|
}
|
|
694
650
|
/** The question, as the item's `legend`. */
|
|
695
651
|
export function QuestionnaireTitle({ className, render, ...props }) {
|
|
696
|
-
useItem(
|
|
652
|
+
useItem('QuestionnaireTitle');
|
|
697
653
|
return useRender({
|
|
698
|
-
defaultTagName:
|
|
654
|
+
defaultTagName: 'legend',
|
|
699
655
|
render,
|
|
700
656
|
props: mergeProps({
|
|
701
|
-
|
|
702
|
-
className: cn(
|
|
657
|
+
'data-slot': 'questionnaire-title',
|
|
658
|
+
className: cn('sprout-questionnaire-title', className),
|
|
703
659
|
}, props),
|
|
704
660
|
});
|
|
705
661
|
}
|
|
706
662
|
/** Help text under the question. The item references it with `aria-describedby`. */
|
|
707
663
|
export function QuestionnaireDescription({ className, id, render, ...props }) {
|
|
708
|
-
const { registerDescription } = useItem(
|
|
664
|
+
const { registerDescription } = useItem('QuestionnaireDescription');
|
|
709
665
|
const generated = useId();
|
|
710
666
|
const resolved = id ?? generated;
|
|
711
667
|
useLayoutEffect(() => registerDescription(resolved), [resolved, registerDescription]);
|
|
712
668
|
return useRender({
|
|
713
|
-
defaultTagName:
|
|
669
|
+
defaultTagName: 'p',
|
|
714
670
|
render,
|
|
715
671
|
props: mergeProps({
|
|
716
|
-
|
|
672
|
+
'data-slot': 'questionnaire-description',
|
|
717
673
|
id: resolved,
|
|
718
|
-
className: cn(
|
|
674
|
+
className: cn('sprout-questionnaire-description', className),
|
|
719
675
|
}, props),
|
|
720
676
|
});
|
|
721
677
|
}
|
|
722
678
|
/** Stacks the item's choices. */
|
|
723
679
|
export function QuestionnaireChoices({ className, render, ...props }) {
|
|
724
|
-
useItem(
|
|
725
|
-
const { shortcuts } = useRoot(
|
|
680
|
+
useItem('QuestionnaireChoices');
|
|
681
|
+
const { shortcuts } = useRoot('QuestionnaireChoices');
|
|
726
682
|
return useRender({
|
|
727
|
-
defaultTagName:
|
|
683
|
+
defaultTagName: 'div',
|
|
728
684
|
render,
|
|
729
685
|
props: mergeProps({
|
|
730
|
-
|
|
731
|
-
className: cn(
|
|
686
|
+
'data-slot': 'questionnaire-choices',
|
|
687
|
+
className: cn('sprout-questionnaire-choices', className),
|
|
732
688
|
}, props),
|
|
733
689
|
state: { shortcuts },
|
|
734
690
|
});
|
|
@@ -738,19 +694,15 @@ export function QuestionnaireChoices({ className, render, ...props }) {
|
|
|
738
694
|
* `multiple` items; the whole row is the hit area.
|
|
739
695
|
*/
|
|
740
696
|
export function QuestionnaireChoice({ checked, children, className, defaultChecked = false, disabled: ownDisabled = false, onChange, render, value, ...props }) {
|
|
741
|
-
const item = useItem(
|
|
697
|
+
const item = useItem('QuestionnaireChoice');
|
|
742
698
|
const { disabled: itemDisabled, hasInputAnswer, invalid, multiple, name, registerAnswerControl, registerAnswerSelection, required, resetVersion, selectedAnswerIds, setAnswerDefault, setAnswerSelectionFromInteraction, shortcutByAnswerId, shortcutByChoiceValue, status, syncControlledAnswerSelection, } = item;
|
|
743
699
|
const id = useId();
|
|
744
700
|
const [input, setInput] = useState(null);
|
|
745
701
|
const initialChecked = useRef(defaultChecked);
|
|
746
702
|
const controlled = checked !== undefined;
|
|
747
703
|
const disabled = itemDisabled || ownDisabled;
|
|
748
|
-
const isChecked = controlled
|
|
749
|
-
|
|
750
|
-
? false
|
|
751
|
-
: checked
|
|
752
|
-
: selectedAnswerIds.includes(id);
|
|
753
|
-
const type = multiple ? "checkbox" : "radio";
|
|
704
|
+
const isChecked = controlled ? (status === 'skipped' ? false : checked) : selectedAnswerIds.includes(id);
|
|
705
|
+
const type = multiple ? 'checkbox' : 'radio';
|
|
754
706
|
const shortcut = shortcutByChoiceValue?.get(value) ?? shortcutByAnswerId.get(id) ?? null;
|
|
755
707
|
useLayoutEffect(() => registerAnswerSelection(id, initialChecked.current), [id, registerAnswerSelection]);
|
|
756
708
|
useLayoutEffect(() => setAnswerDefault(id, defaultChecked), [id, defaultChecked, setAnswerDefault]);
|
|
@@ -758,7 +710,7 @@ export function QuestionnaireChoice({ checked, children, className, defaultCheck
|
|
|
758
710
|
if (!input)
|
|
759
711
|
return;
|
|
760
712
|
return registerAnswerControl({
|
|
761
|
-
type:
|
|
713
|
+
type: 'choice',
|
|
762
714
|
id,
|
|
763
715
|
element: input,
|
|
764
716
|
disabled,
|
|
@@ -785,7 +737,7 @@ export function QuestionnaireChoice({ checked, children, className, defaultCheck
|
|
|
785
737
|
setAnswerSelectionFromInteraction(id, event.target.checked);
|
|
786
738
|
return;
|
|
787
739
|
}
|
|
788
|
-
if (status ===
|
|
740
|
+
if (status === 'skipped' && checked === event.target.checked)
|
|
789
741
|
setAnswerSelectionFromInteraction(id, checked);
|
|
790
742
|
}
|
|
791
743
|
const state = {
|
|
@@ -796,29 +748,29 @@ export function QuestionnaireChoice({ checked, children, className, defaultCheck
|
|
|
796
748
|
type,
|
|
797
749
|
};
|
|
798
750
|
return useRender({
|
|
799
|
-
defaultTagName:
|
|
751
|
+
defaultTagName: 'label',
|
|
800
752
|
render,
|
|
801
753
|
props: mergeProps({
|
|
802
|
-
|
|
803
|
-
className: cn(
|
|
804
|
-
children: (_jsxs(_Fragment, { children: [_jsx("input", { "data-slot": "questionnaire-choice-input", className: "sprout-questionnaire-choice-input", ref: setInput, id: id, type: type, name: status ===
|
|
754
|
+
'data-slot': 'questionnaire-choice',
|
|
755
|
+
className: cn('sprout-questionnaire-choice', className),
|
|
756
|
+
children: (_jsxs(_Fragment, { children: [_jsx("input", { "data-slot": "questionnaire-choice-input", className: "sprout-questionnaire-choice-input", ref: setInput, id: id, type: type, name: status === 'skipped' ? undefined : name, value: value, checked: isChecked, disabled: disabled, required: required && !multiple && !hasInputAnswer, "aria-invalid": invalid || undefined, "aria-keyshortcuts": keyShortcuts(shortcut, !disabled && isChecked), "data-checked": isChecked ? '' : undefined, "data-unchecked": isChecked ? undefined : '', onChange: handleChange }), _jsxs("span", { "aria-hidden": "true", "data-slot": "questionnaire-choice-indicator", className: "sprout-questionnaire-choice-indicator", children: [_jsx("span", { className: "sprout-questionnaire-choice-dot" }), _jsx(Check, { className: "sprout-questionnaire-choice-check" })] }), _jsx("span", { "data-slot": "questionnaire-choice-label", className: "sprout-questionnaire-choice-label", children: children }), shortcut !== null && (_jsx(Kbd, { variant: "outline", "aria-hidden": "true", "data-slot": "questionnaire-choice-shortcut", className: "sprout-questionnaire-choice-shortcut", children: shortcut }))] })),
|
|
805
757
|
}, props),
|
|
806
758
|
state,
|
|
807
759
|
stateAttributesMapping: {
|
|
808
|
-
checked: (on) => on ? {
|
|
760
|
+
checked: (on) => (on ? { 'data-checked': '' } : { 'data-unchecked': '' }),
|
|
809
761
|
},
|
|
810
762
|
});
|
|
811
763
|
}
|
|
812
764
|
/** A muted second line inside a choice. */
|
|
813
765
|
export function QuestionnaireChoiceDescription({ className, ...props }) {
|
|
814
|
-
return (_jsx("span", { "data-slot": "questionnaire-choice-description", className: cn(
|
|
766
|
+
return (_jsx("span", { "data-slot": "questionnaire-choice-description", className: cn('sprout-questionnaire-choice-description', className), ...props }));
|
|
815
767
|
}
|
|
816
768
|
/**
|
|
817
769
|
* A typed answer. It counts as answered once it has text, and is only sent
|
|
818
770
|
* with the form while it does.
|
|
819
771
|
*/
|
|
820
|
-
export function QuestionnaireInput({ className, defaultValue, disabled: ownDisabled = false, onChange, ref, render, type =
|
|
821
|
-
const { disabled: itemDisabled, invalid, name, registerAnswerControl, registerAnswerSelection, resetVersion, selectedAnswerIds, setAnswerDefault, setAnswerSelectionFromInteraction, syncControlledAnswerSelection, } = useItem(
|
|
772
|
+
export function QuestionnaireInput({ className, defaultValue, disabled: ownDisabled = false, onChange, ref, render, type = 'text', value, ...props }) {
|
|
773
|
+
const { disabled: itemDisabled, invalid, name, registerAnswerControl, registerAnswerSelection, resetVersion, selectedAnswerIds, setAnswerDefault, setAnswerSelectionFromInteraction, syncControlledAnswerSelection, } = useItem('QuestionnaireInput');
|
|
822
774
|
const id = useId();
|
|
823
775
|
const element = useRef(null);
|
|
824
776
|
const initialFilled = useRef(hasText(defaultValue));
|
|
@@ -836,7 +788,7 @@ export function QuestionnaireInput({ className, defaultValue, disabled: ownDisab
|
|
|
836
788
|
if (!node)
|
|
837
789
|
return;
|
|
838
790
|
return registerAnswerControl({
|
|
839
|
-
type:
|
|
791
|
+
type: 'input',
|
|
840
792
|
id,
|
|
841
793
|
element: node,
|
|
842
794
|
disabled,
|
|
@@ -849,15 +801,7 @@ export function QuestionnaireInput({ className, defaultValue, disabled: ownDisab
|
|
|
849
801
|
}
|
|
850
802
|
if (resetVersion > 0)
|
|
851
803
|
setFilledState(defaultFilled);
|
|
852
|
-
}, [
|
|
853
|
-
controlled,
|
|
854
|
-
valueFilled,
|
|
855
|
-
value,
|
|
856
|
-
defaultFilled,
|
|
857
|
-
id,
|
|
858
|
-
resetVersion,
|
|
859
|
-
syncControlledAnswerSelection,
|
|
860
|
-
]);
|
|
804
|
+
}, [controlled, valueFilled, value, defaultFilled, id, resetVersion, syncControlledAnswerSelection]);
|
|
861
805
|
useLayoutEffect(() => {
|
|
862
806
|
const node = element.current;
|
|
863
807
|
if (node && controlled)
|
|
@@ -876,16 +820,16 @@ export function QuestionnaireInput({ className, defaultValue, disabled: ownDisab
|
|
|
876
820
|
assignRef(ref, node);
|
|
877
821
|
}, [ref]);
|
|
878
822
|
const input = useRender({
|
|
879
|
-
defaultTagName:
|
|
823
|
+
defaultTagName: 'input',
|
|
880
824
|
render,
|
|
881
825
|
props: mergeProps({
|
|
882
|
-
|
|
883
|
-
className: cn(
|
|
884
|
-
|
|
885
|
-
|
|
826
|
+
'data-slot': 'questionnaire-input',
|
|
827
|
+
className: cn('sprout-input', 'sprout-questionnaire-input', className),
|
|
828
|
+
'aria-invalid': invalid || undefined,
|
|
829
|
+
'aria-keyshortcuts': keyShortcuts(null, !disabled && filled && selected),
|
|
886
830
|
defaultValue: controlled ? undefined : defaultValue,
|
|
887
831
|
disabled,
|
|
888
|
-
form: selected ? undefined :
|
|
832
|
+
form: selected ? undefined : '',
|
|
889
833
|
id,
|
|
890
834
|
name: selected ? name : undefined,
|
|
891
835
|
onChange: handleChange,
|
|
@@ -895,40 +839,37 @@ export function QuestionnaireInput({ className, defaultValue, disabled: ownDisab
|
|
|
895
839
|
}, props),
|
|
896
840
|
state: { disabled, filled, invalid },
|
|
897
841
|
stateAttributesMapping: {
|
|
898
|
-
filled: (on) => on ? {
|
|
842
|
+
filled: (on) => (on ? { 'data-filled': '' } : { 'data-empty': '' }),
|
|
899
843
|
},
|
|
900
844
|
});
|
|
901
845
|
return (_jsx("div", { "data-slot": "questionnaire-input-wrapper", className: "sprout-questionnaire-input-wrapper", children: input }));
|
|
902
846
|
}
|
|
903
847
|
/** Shown after someone tries to move on without a valid answer. */
|
|
904
848
|
export function QuestionnaireError({ children, className, id, render, ...props }) {
|
|
905
|
-
const { invalid, registerError, required } = useItem(
|
|
849
|
+
const { invalid, registerError, required } = useItem('QuestionnaireError');
|
|
906
850
|
const generated = useId();
|
|
907
851
|
const resolved = id ?? generated;
|
|
908
852
|
useLayoutEffect(() => registerError(resolved), [resolved, registerError]);
|
|
909
853
|
return useRender({
|
|
910
|
-
defaultTagName:
|
|
854
|
+
defaultTagName: 'p',
|
|
911
855
|
render,
|
|
912
856
|
props: mergeProps({
|
|
913
|
-
|
|
914
|
-
className: cn(
|
|
857
|
+
'data-slot': 'questionnaire-error',
|
|
858
|
+
className: cn('sprout-questionnaire-error', className),
|
|
915
859
|
id: resolved,
|
|
916
860
|
hidden: !invalid,
|
|
917
|
-
role: invalid ?
|
|
918
|
-
children: children ??
|
|
919
|
-
(required
|
|
920
|
-
? "Choose an answer to continue."
|
|
921
|
-
: "Choose an answer or skip this question."),
|
|
861
|
+
role: invalid ? 'alert' : undefined,
|
|
862
|
+
children: children ?? (required ? 'Choose an answer to continue.' : 'Choose an answer or skip this question.'),
|
|
922
863
|
}, props),
|
|
923
864
|
state: { invalid },
|
|
924
865
|
});
|
|
925
866
|
}
|
|
926
867
|
/** Previous on the start side; Skip and Next or Submit on the end side. */
|
|
927
868
|
export function QuestionnaireActions({ className, ...props }) {
|
|
928
|
-
return
|
|
869
|
+
return _jsx("div", { "data-slot": "questionnaire-actions", className: cn('sprout-questionnaire-actions', className), ...props });
|
|
929
870
|
}
|
|
930
|
-
function useNavigationButton(slot, { children, className, disabled = false, onClick, render, size =
|
|
931
|
-
const resolvedVariant = variant ?? (slot ===
|
|
871
|
+
function useNavigationButton(slot, { children, className, disabled = false, onClick, render, size = 'default', tabIndex, type, variant, ...props }, { label, visible, status, shortcut, action, }) {
|
|
872
|
+
const resolvedVariant = variant ?? (slot === 'next' || slot === 'submit' ? 'default' : 'outline');
|
|
932
873
|
const activeShortcut = visible && !disabled ? (shortcut ?? null) : null;
|
|
933
874
|
function handleClick(event) {
|
|
934
875
|
onClick?.(event);
|
|
@@ -936,34 +877,34 @@ function useNavigationButton(slot, { children, className, disabled = false, onCl
|
|
|
936
877
|
action?.();
|
|
937
878
|
}
|
|
938
879
|
return useRender({
|
|
939
|
-
defaultTagName:
|
|
880
|
+
defaultTagName: 'button',
|
|
940
881
|
render,
|
|
941
882
|
props: mergeProps({
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
className: cn(buttonVariants({ variant: resolvedVariant, size }),
|
|
946
|
-
|
|
947
|
-
|
|
883
|
+
'data-slot': `questionnaire-${slot}`,
|
|
884
|
+
'data-size': size,
|
|
885
|
+
'data-variant': resolvedVariant,
|
|
886
|
+
className: cn(buttonVariants({ variant: resolvedVariant, size }), 'sprout-questionnaire-action', `sprout-questionnaire-${slot}`, className),
|
|
887
|
+
'aria-hidden': !visible || undefined,
|
|
888
|
+
'aria-keyshortcuts': activeShortcut ?? undefined,
|
|
948
889
|
children: children ?? label,
|
|
949
890
|
disabled,
|
|
950
891
|
hidden: !visible,
|
|
951
892
|
inert: !visible,
|
|
952
893
|
onClick: action ? handleClick : onClick,
|
|
953
894
|
tabIndex: visible ? tabIndex : -1,
|
|
954
|
-
type: type ?? (slot ===
|
|
895
|
+
type: type ?? (slot === 'submit' ? 'submit' : 'button'),
|
|
955
896
|
}, props),
|
|
956
897
|
state: { disabled, shortcut: activeShortcut, status, visible },
|
|
957
898
|
stateAttributesMapping: {
|
|
958
|
-
visible: (on) => on ? {
|
|
899
|
+
visible: (on) => (on ? { 'data-visible': '' } : { 'data-hidden': '' }),
|
|
959
900
|
},
|
|
960
901
|
});
|
|
961
902
|
}
|
|
962
903
|
/** Goes back one item. Hidden on the first item. */
|
|
963
904
|
export function QuestionnairePrevious(props) {
|
|
964
|
-
const root = useRoot(
|
|
965
|
-
return useNavigationButton(
|
|
966
|
-
label:
|
|
905
|
+
const root = useRoot('QuestionnairePrevious');
|
|
906
|
+
return useNavigationButton('previous', props, {
|
|
907
|
+
label: 'Previous',
|
|
967
908
|
visible: root.total > 1 && !root.first,
|
|
968
909
|
status: root.activeItemStatus,
|
|
969
910
|
action: root.goPrevious,
|
|
@@ -971,9 +912,9 @@ export function QuestionnairePrevious(props) {
|
|
|
971
912
|
}
|
|
972
913
|
/** Clears the answer and moves on. Shown only on optional items; submits on the last one. */
|
|
973
914
|
export function QuestionnaireSkip(props) {
|
|
974
|
-
const root = useRoot(
|
|
975
|
-
return useNavigationButton(
|
|
976
|
-
label:
|
|
915
|
+
const root = useRoot('QuestionnaireSkip');
|
|
916
|
+
return useNavigationButton('skip', props, {
|
|
917
|
+
label: 'Skip',
|
|
977
918
|
visible: root.activeItemRequired === false,
|
|
978
919
|
status: root.activeItemStatus,
|
|
979
920
|
action: root.skipCurrent,
|
|
@@ -981,22 +922,22 @@ export function QuestionnaireSkip(props) {
|
|
|
981
922
|
}
|
|
982
923
|
/** Checks the answer and moves on. Hidden on the last item. */
|
|
983
924
|
export function QuestionnaireNext(props) {
|
|
984
|
-
const root = useRoot(
|
|
985
|
-
return useNavigationButton(
|
|
986
|
-
label:
|
|
925
|
+
const root = useRoot('QuestionnaireNext');
|
|
926
|
+
return useNavigationButton('next', props, {
|
|
927
|
+
label: 'Next',
|
|
987
928
|
visible: root.total > 1 && !root.last,
|
|
988
929
|
status: root.activeItemStatus,
|
|
989
|
-
shortcut:
|
|
930
|
+
shortcut: 'Enter',
|
|
990
931
|
action: root.goNext,
|
|
991
932
|
});
|
|
992
933
|
}
|
|
993
934
|
/** Submits the form. Shown only on the last item. */
|
|
994
935
|
export function QuestionnaireSubmit(props) {
|
|
995
|
-
const root = useRoot(
|
|
996
|
-
return useNavigationButton(
|
|
997
|
-
label:
|
|
936
|
+
const root = useRoot('QuestionnaireSubmit');
|
|
937
|
+
return useNavigationButton('submit', props, {
|
|
938
|
+
label: 'Submit',
|
|
998
939
|
visible: root.total > 0 && root.last,
|
|
999
940
|
status: root.activeItemStatus,
|
|
1000
|
-
shortcut:
|
|
941
|
+
shortcut: 'Enter',
|
|
1001
942
|
});
|
|
1002
943
|
}
|