@nucleux/questionnaire 0.0.0-stage → 0.7.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/dist/index.cjs ADDED
@@ -0,0 +1,863 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React = require('react');
5
+ var lucideReact = require('lucide-react');
6
+ var utils = require('@nucleux/utils');
7
+ var button = require('@nucleux/button');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
29
+
30
+ // src/index.tsx
31
+ var useIsoLayoutEffect = typeof window !== "undefined" ? React__namespace.useLayoutEffect : React__namespace.useEffect;
32
+ function mergeRefs(...refs) {
33
+ return (node) => {
34
+ for (const ref of refs) {
35
+ if (typeof ref === "function") ref(node);
36
+ else if (ref) ref.current = node;
37
+ }
38
+ };
39
+ }
40
+ function renderPart(tag, props, state, render) {
41
+ if (typeof render === "function") return render(props, state);
42
+ if (React__namespace.isValidElement(render)) {
43
+ const element = render;
44
+ const theirs = element.props;
45
+ const merged = { ...props, ...theirs };
46
+ for (const key of Object.keys(props)) {
47
+ const ours = props[key];
48
+ const other = theirs[key];
49
+ if (/^on[A-Z]/.test(key) && typeof ours === "function" && typeof other === "function") {
50
+ merged[key] = (...args) => {
51
+ other(...args);
52
+ ours(...args);
53
+ };
54
+ } else if (key === "className") {
55
+ merged.className = utils.cn(ours, other);
56
+ } else if (key === "children" && other === void 0) {
57
+ merged.children = ours;
58
+ }
59
+ }
60
+ return React__namespace.cloneElement(element, merged);
61
+ }
62
+ return React__namespace.createElement(tag, props);
63
+ }
64
+ var flag = (on) => on ? "" : void 0;
65
+ var CONTROL = "input[data-questionnaire-control]";
66
+ function readAnswer(fieldset) {
67
+ const checked = [];
68
+ let text = "";
69
+ fieldset.querySelectorAll(CONTROL).forEach((input) => {
70
+ if (input.matches(":disabled")) return;
71
+ if (input.type === "radio" || input.type === "checkbox") {
72
+ if (input.checked) checked.push(input.value);
73
+ } else if (!text && input.value.trim() !== "") {
74
+ text = input.value;
75
+ }
76
+ });
77
+ return { checked, text };
78
+ }
79
+ var sameAnswer = (a, b) => !!a && a.text === b.text && a.checked.length === b.checked.length && a.checked.every((v, i) => v === b.checked[i]);
80
+ var hasAnswer = (a) => !!a && (a.checked.length > 0 || a.text.trim() !== "");
81
+ function shortcutLabel(scheme, index) {
82
+ if (!scheme || index < 0) return void 0;
83
+ if (scheme === "letters") return index < 26 ? String.fromCharCode(65 + index) : void 0;
84
+ return index < 9 ? String(index + 1) : void 0;
85
+ }
86
+ var RootContext = React__namespace.createContext(null);
87
+ function useRoot(part) {
88
+ const ctx = React__namespace.useContext(RootContext);
89
+ if (!ctx) throw new Error(`<${part}> must be used inside <Questionnaire>.`);
90
+ return ctx;
91
+ }
92
+ var ItemContext = React__namespace.createContext(null);
93
+ function useItem(part) {
94
+ const ctx = React__namespace.useContext(ItemContext);
95
+ if (!ctx) throw new Error(`<${part}> must be used inside <QuestionnaireItem>.`);
96
+ return ctx;
97
+ }
98
+ var Questionnaire = React__namespace.forwardRef(
99
+ ({
100
+ item: itemProp,
101
+ defaultItem,
102
+ items,
103
+ onItemChange,
104
+ shortcuts,
105
+ noValidate = true,
106
+ onSubmit,
107
+ onReset,
108
+ onKeyDown,
109
+ className,
110
+ children,
111
+ ...props
112
+ }, ref) => {
113
+ const formRef = React__namespace.useRef(null);
114
+ const elements = React__namespace.useRef(/* @__PURE__ */ new Map());
115
+ const initialItem = React__namespace.useRef(defaultItem);
116
+ const pendingFocus = React__namespace.useRef(null);
117
+ const [metas, setMetas] = React__namespace.useState({});
118
+ const [internalItem, setInternalItem] = React__namespace.useState(defaultItem);
119
+ const [answers, setAnswers] = React__namespace.useState({});
120
+ const [skipped, setSkipped] = React__namespace.useState({});
121
+ const [invalid, setInvalid] = React__namespace.useState({});
122
+ const [, setTick] = React__namespace.useState(0);
123
+ const definitions = React__namespace.useMemo(
124
+ () => new Map((items ?? []).map((d) => [d.name, d])),
125
+ [items]
126
+ );
127
+ const metaOf = (name) => {
128
+ const def = definitions.get(name);
129
+ return metas[name] ?? {
130
+ required: def?.required ?? false,
131
+ multiple: def?.multiple ?? false,
132
+ disabled: def?.disabled ?? false
133
+ };
134
+ };
135
+ const domOrder = Object.keys(metas).sort((a, b) => {
136
+ const ea = elements.current.get(a);
137
+ const eb = elements.current.get(b);
138
+ if (!ea || !eb) return 0;
139
+ return ea.compareDocumentPosition(eb) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
140
+ });
141
+ const order = items ? [...items.map((d) => d.name), ...domOrder.filter((n) => !definitions.has(n))] : domOrder;
142
+ const enabled = order.filter((n) => !metaOf(n).disabled);
143
+ const requested = itemProp !== void 0 ? itemProp : internalItem;
144
+ const active = requested !== void 0 && enabled.includes(requested) ? requested : enabled[0];
145
+ const index = active !== void 0 ? enabled.indexOf(active) : -1;
146
+ const isLast = index === enabled.length - 1;
147
+ const answered = (name) => hasAnswer(answers[name]);
148
+ const statusOf = (name) => answered(name) ? "answered" : skipped[name] ? "skipped" : "unanswered";
149
+ const isValid = (name) => answered(name) || !metaOf(name).required && !!skipped[name];
150
+ const goTo = (name, target) => {
151
+ pendingFocus.current = { name, target };
152
+ if (name !== active) {
153
+ if (itemProp === void 0) setInternalItem(name);
154
+ onItemChange?.(name);
155
+ }
156
+ setTick((t) => t + 1);
157
+ };
158
+ const markInvalid = (names) => setInvalid((prev) => {
159
+ const nextState = { ...prev };
160
+ for (const n of names) nextState[n] = true;
161
+ return nextState;
162
+ });
163
+ const clearFlag = (setter, name) => setter((prev) => {
164
+ if (!prev[name]) return prev;
165
+ const nextState = { ...prev };
166
+ delete nextState[name];
167
+ return nextState;
168
+ });
169
+ const next = () => {
170
+ if (active === void 0) return;
171
+ if (!isValid(active)) {
172
+ markInvalid([active]);
173
+ goTo(active, "control");
174
+ return;
175
+ }
176
+ clearFlag(setInvalid, active);
177
+ const target = enabled[index + 1];
178
+ if (target !== void 0) goTo(target, "item");
179
+ };
180
+ const previous = () => {
181
+ const target = enabled[index - 1];
182
+ if (index > 0 && target !== void 0) goTo(target, "item");
183
+ };
184
+ const skip = () => {
185
+ if (active === void 0 || metaOf(active).required) return;
186
+ const el = elements.current.get(active);
187
+ el?.querySelectorAll(CONTROL).forEach((input) => {
188
+ if (input.type === "radio" || input.type === "checkbox") input.checked = false;
189
+ else input.value = "";
190
+ });
191
+ setAnswers((prev) => ({ ...prev, [active]: { checked: [], text: "" } }));
192
+ setSkipped((prev) => ({ ...prev, [active]: true }));
193
+ clearFlag(setInvalid, active);
194
+ const target = enabled[index + 1];
195
+ if (target !== void 0) goTo(target, "item");
196
+ };
197
+ const continueFlow = () => {
198
+ if (active === void 0) return;
199
+ if (!isLast) {
200
+ next();
201
+ return;
202
+ }
203
+ const form = formRef.current;
204
+ if (!form) return;
205
+ if (typeof form.requestSubmit === "function") form.requestSubmit();
206
+ else form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
207
+ };
208
+ React__namespace.useEffect(() => {
209
+ const pending = pendingFocus.current;
210
+ if (!pending || pending.name !== active) return;
211
+ const el = elements.current.get(pending.name);
212
+ if (!el || el.hidden) return;
213
+ pendingFocus.current = null;
214
+ if (pending.target === "control") {
215
+ const control = Array.from(el.querySelectorAll(CONTROL)).find(
216
+ (c) => !c.matches(":disabled")
217
+ );
218
+ (control ?? el).focus();
219
+ } else {
220
+ el.focus();
221
+ }
222
+ });
223
+ const handleSubmit = (event) => {
224
+ const failing = enabled.filter((n) => !isValid(n));
225
+ if (failing.length > 0) {
226
+ event.preventDefault();
227
+ markInvalid(failing);
228
+ goTo(failing[0], "control");
229
+ return;
230
+ }
231
+ onSubmit?.(event);
232
+ };
233
+ const handleReset = (event) => {
234
+ onReset?.(event);
235
+ if (event.defaultPrevented) return;
236
+ setSkipped({});
237
+ setInvalid({});
238
+ if (itemProp === void 0) setInternalItem(initialItem.current);
239
+ else if (initialItem.current !== void 0) onItemChange?.(initialItem.current);
240
+ setTimeout(() => setTick((t) => t + 1), 0);
241
+ };
242
+ const handleKeyDown = (event) => {
243
+ onKeyDown?.(event);
244
+ if (event.defaultPrevented) return;
245
+ const target = event.target;
246
+ const fieldset = active !== void 0 ? elements.current.get(active) : void 0;
247
+ const input = target instanceof HTMLInputElement ? target : null;
248
+ const isChoiceInput = !!input && (input.type === "radio" || input.type === "checkbox");
249
+ const isTextEntry = !!input && !isChoiceInput && !["button", "submit", "reset"].includes(input.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.isContentEditable;
250
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
251
+ event.preventDefault();
252
+ continueFlow();
253
+ return;
254
+ }
255
+ if (event.key === "Enter") {
256
+ if (input && (isChoiceInput || isTextEntry)) {
257
+ event.preventDefault();
258
+ if (active !== void 0 && answered(active)) continueFlow();
259
+ }
260
+ return;
261
+ }
262
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
263
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
264
+ if (!fieldset || !fieldset.contains(target)) return;
265
+ if (isTextEntry && !(input && input.value === "")) return;
266
+ const controls = Array.from(fieldset.querySelectorAll(CONTROL)).filter(
267
+ (c) => !c.matches(":disabled")
268
+ );
269
+ if (controls.length === 0) return;
270
+ event.preventDefault();
271
+ const i = input ? controls.indexOf(input) : -1;
272
+ const n = controls.length;
273
+ const nextIndex = event.key === "ArrowDown" ? i < 0 ? 0 : (i + 1) % n : i < 0 ? n - 1 : (i - 1 + n) % n;
274
+ const control = controls[nextIndex];
275
+ control.focus();
276
+ if (control.type === "radio" && !control.checked) control.click();
277
+ return;
278
+ }
279
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
280
+ if (isTextEntry || input && input.type === "radio") return;
281
+ if (event.key === "ArrowLeft") {
282
+ if (index > 0) {
283
+ event.preventDefault();
284
+ previous();
285
+ }
286
+ } else if (active !== void 0 && !isLast && statusOf(active) !== "unanswered") {
287
+ event.preventDefault();
288
+ next();
289
+ }
290
+ return;
291
+ }
292
+ if (shortcuts && fieldset && !isTextEntry && event.key.length === 1) {
293
+ const key = shortcuts === "letters" ? event.key.toUpperCase() : event.key;
294
+ if (!(shortcuts === "letters" ? /^[A-Z]$/ : /^[1-9]$/).test(key)) return;
295
+ const choice = fieldset.querySelector(`[data-shortcut="${key}"]`);
296
+ const control = choice?.querySelector(CONTROL);
297
+ if (control && !control.matches(":disabled")) {
298
+ event.preventDefault();
299
+ control.focus();
300
+ control.click();
301
+ }
302
+ }
303
+ };
304
+ const register = React__namespace.useCallback((name, meta) => {
305
+ setMetas((prev) => {
306
+ const old = prev[name];
307
+ if (old && old.required === meta.required && old.multiple === meta.multiple && old.disabled === meta.disabled)
308
+ return prev;
309
+ return { ...prev, [name]: meta };
310
+ });
311
+ }, []);
312
+ const unregister = React__namespace.useCallback((name) => {
313
+ setMetas((prev) => {
314
+ if (!(name in prev)) return prev;
315
+ const nextState = { ...prev };
316
+ delete nextState[name];
317
+ return nextState;
318
+ });
319
+ }, []);
320
+ const setElement = React__namespace.useCallback((name, el) => {
321
+ if (el) elements.current.set(name, el);
322
+ else elements.current.delete(name);
323
+ }, []);
324
+ const setAnswer = React__namespace.useCallback((name, answer) => {
325
+ setAnswers((prev) => sameAnswer(prev[name], answer) ? prev : { ...prev, [name]: answer });
326
+ if (hasAnswer(answer)) {
327
+ const drop = (prev) => {
328
+ if (!prev[name]) return prev;
329
+ const nextState = { ...prev };
330
+ delete nextState[name];
331
+ return nextState;
332
+ };
333
+ setSkipped(drop);
334
+ setInvalid(drop);
335
+ }
336
+ }, []);
337
+ const context = {
338
+ shortcuts,
339
+ definitions,
340
+ active,
341
+ index,
342
+ total: enabled.length,
343
+ answers,
344
+ invalid,
345
+ register,
346
+ unregister,
347
+ setElement,
348
+ setAnswer,
349
+ statusOf,
350
+ isRequired: (name) => metaOf(name).required,
351
+ next,
352
+ previous,
353
+ skip
354
+ };
355
+ return /* @__PURE__ */ jsxRuntime.jsx(RootContext.Provider, { value: context, children: /* @__PURE__ */ jsxRuntime.jsx(
356
+ "form",
357
+ {
358
+ ref: mergeRefs(ref, formRef),
359
+ noValidate,
360
+ "data-slot": "questionnaire",
361
+ "data-current": index + 1,
362
+ "data-total": enabled.length,
363
+ "data-first": flag(index === 0),
364
+ "data-last": flag(enabled.length > 0 && isLast),
365
+ "data-shortcuts": shortcuts,
366
+ className: utils.cn("flex w-full min-w-0 flex-col gap-4", className),
367
+ onSubmit: handleSubmit,
368
+ onReset: handleReset,
369
+ onKeyDown: handleKeyDown,
370
+ ...props,
371
+ children
372
+ }
373
+ ) });
374
+ }
375
+ );
376
+ Questionnaire.displayName = "Questionnaire";
377
+ var QuestionnaireProgress = React__namespace.forwardRef(
378
+ ({ className, render, children, ...props }, ref) => {
379
+ const root = useRoot("QuestionnaireProgress");
380
+ const current = root.index + 1;
381
+ const total = root.total;
382
+ const state = {
383
+ current,
384
+ total,
385
+ first: root.index === 0,
386
+ last: total > 0 && current === total
387
+ };
388
+ const text = `Question ${current} of ${total}`;
389
+ return renderPart(
390
+ "div",
391
+ {
392
+ ref,
393
+ role: "progressbar",
394
+ "aria-label": "Questionnaire progress",
395
+ "aria-valuemin": 0,
396
+ "aria-valuemax": total,
397
+ "aria-valuenow": current,
398
+ "aria-valuetext": text,
399
+ "data-slot": "questionnaire-progress",
400
+ "data-current": current,
401
+ "data-total": total,
402
+ "data-first": flag(state.first),
403
+ "data-last": flag(state.last),
404
+ className: utils.cn(
405
+ "min-h-[1lh] w-fit min-w-[14ch] text-xs font-medium tabular-nums text-muted-foreground",
406
+ className
407
+ ),
408
+ ...props,
409
+ children: children ?? text
410
+ },
411
+ state,
412
+ render
413
+ );
414
+ }
415
+ );
416
+ QuestionnaireProgress.displayName = "QuestionnaireProgress";
417
+ var QuestionnaireItem = React__namespace.forwardRef(
418
+ ({
419
+ name,
420
+ required = false,
421
+ multiple = false,
422
+ disabled = false,
423
+ invalid: invalidProp = false,
424
+ onStatusChange,
425
+ onChange,
426
+ className,
427
+ children,
428
+ "aria-describedby": describedByProp,
429
+ ...props
430
+ }, ref) => {
431
+ const root = useRoot("QuestionnaireItem");
432
+ const elRef = React__namespace.useRef(null);
433
+ const baseId = React__namespace.useId();
434
+ const [hasDescription, setHasDescription] = React__namespace.useState(false);
435
+ const [hasError, setHasError] = React__namespace.useState(false);
436
+ const [domChoices, setDomChoices] = React__namespace.useState([]);
437
+ const { register, unregister, setElement, setAnswer } = root;
438
+ useIsoLayoutEffect(() => {
439
+ register(name, { required, multiple, disabled });
440
+ }, [register, name, required, multiple, disabled]);
441
+ useIsoLayoutEffect(() => () => unregister(name), [unregister, name]);
442
+ useIsoLayoutEffect(() => {
443
+ const el = elRef.current;
444
+ if (!el) return;
445
+ setAnswer(name, readAnswer(el));
446
+ const values = Array.from(el.querySelectorAll("[data-questionnaire-choice-value]")).filter((c) => !c.hasAttribute("data-disabled")).map((c) => c.getAttribute("data-questionnaire-choice-value") ?? "");
447
+ setDomChoices(
448
+ (prev) => prev.length === values.length && prev.every((v, i) => v === values[i]) ? prev : values
449
+ );
450
+ });
451
+ const active = root.active === name && !disabled;
452
+ const status = root.statusOf(name);
453
+ const invalid = invalidProp || !!root.invalid[name];
454
+ const lastStatus = React__namespace.useRef(status);
455
+ React__namespace.useEffect(() => {
456
+ if (lastStatus.current !== status) {
457
+ lastStatus.current = status;
458
+ onStatusChange?.(status);
459
+ }
460
+ }, [status, onStatusChange]);
461
+ const definedChoices = root.definitions.get(name)?.choices;
462
+ const choiceOrder = definedChoices ? definedChoices.filter((c) => !c.disabled).map((c) => c.value) : domChoices;
463
+ const answer = root.answers[name];
464
+ const descriptionId = `${baseId}-description`;
465
+ const errorId = `${baseId}-error`;
466
+ const describedBy = [describedByProp, hasDescription && descriptionId, invalid && hasError && errorId].filter(Boolean).join(" ") || void 0;
467
+ const handleChange = (event) => {
468
+ onChange?.(event);
469
+ const el = elRef.current;
470
+ const target = event.target;
471
+ if (!el || !target.matches?.(CONTROL)) return;
472
+ if (!multiple) {
473
+ if (target.type === "radio" && target.checked) {
474
+ el.querySelectorAll(`${CONTROL}[data-questionnaire-control="input"]`).forEach((i) => {
475
+ i.value = "";
476
+ });
477
+ } else if (target.getAttribute("data-questionnaire-control") === "input" && target.value.trim() !== "") {
478
+ el.querySelectorAll(`${CONTROL}[type="radio"]`).forEach((i) => {
479
+ i.checked = false;
480
+ });
481
+ }
482
+ }
483
+ setAnswer(name, readAnswer(el));
484
+ };
485
+ const itemContext = {
486
+ name,
487
+ multiple,
488
+ required,
489
+ disabled,
490
+ invalid,
491
+ measured: answer !== void 0,
492
+ checked: answer?.checked ?? [],
493
+ text: answer?.text ?? "",
494
+ descriptionId,
495
+ errorId,
496
+ setHasDescription,
497
+ setHasError,
498
+ shortcutFor: (value) => shortcutLabel(root.shortcuts, choiceOrder.indexOf(value))
499
+ };
500
+ return /* @__PURE__ */ jsxRuntime.jsx(ItemContext.Provider, { value: itemContext, children: /* @__PURE__ */ jsxRuntime.jsx(
501
+ "fieldset",
502
+ {
503
+ ref: mergeRefs(ref, elRef, (el) => setElement(name, el)),
504
+ tabIndex: -1,
505
+ disabled,
506
+ hidden: !active,
507
+ "aria-describedby": describedBy,
508
+ "aria-invalid": invalid || void 0,
509
+ "data-slot": "questionnaire-item",
510
+ "data-active": flag(active),
511
+ "data-status": status,
512
+ "data-required": flag(required),
513
+ "data-multiple": flag(multiple),
514
+ "data-disabled": flag(disabled),
515
+ "data-invalid": flag(invalid),
516
+ className: utils.cn(
517
+ "m-0 flex min-w-0 flex-col gap-4 border-0 p-0 outline-none [&[hidden]]:hidden",
518
+ className
519
+ ),
520
+ onChange: handleChange,
521
+ ...props,
522
+ children
523
+ }
524
+ ) });
525
+ }
526
+ );
527
+ QuestionnaireItem.displayName = "QuestionnaireItem";
528
+ var QuestionnaireTitle = React__namespace.forwardRef(
529
+ ({ className, render, ...props }, ref) => renderPart(
530
+ "legend",
531
+ {
532
+ ref,
533
+ "data-slot": "questionnaire-title",
534
+ className: utils.cn(
535
+ "text-pretty text-base font-medium leading-snug [&:not(:has(~[data-slot=questionnaire-description]))]:mb-4",
536
+ className
537
+ ),
538
+ ...props
539
+ },
540
+ {},
541
+ render
542
+ )
543
+ );
544
+ QuestionnaireTitle.displayName = "QuestionnaireTitle";
545
+ var QuestionnaireDescription = React__namespace.forwardRef(
546
+ ({ className, render, id, ...props }, ref) => {
547
+ const item = useItem("QuestionnaireDescription");
548
+ const { setHasDescription } = item;
549
+ React__namespace.useEffect(() => {
550
+ setHasDescription(true);
551
+ return () => setHasDescription(false);
552
+ }, [setHasDescription]);
553
+ return renderPart(
554
+ "p",
555
+ {
556
+ ref,
557
+ id: id ?? item.descriptionId,
558
+ "data-slot": "questionnaire-description",
559
+ className: utils.cn("text-pretty text-sm text-muted-foreground", className),
560
+ ...props
561
+ },
562
+ {},
563
+ render
564
+ );
565
+ }
566
+ );
567
+ QuestionnaireDescription.displayName = "QuestionnaireDescription";
568
+ var QuestionnaireChoices = React__namespace.forwardRef(
569
+ ({ className, render, ...props }, ref) => {
570
+ const root = useRoot("QuestionnaireChoices");
571
+ return renderPart(
572
+ "div",
573
+ {
574
+ ref,
575
+ "data-slot": "questionnaire-choices",
576
+ "data-shortcuts": root.shortcuts,
577
+ className: utils.cn("group/questionnaire-choices grid min-w-0 gap-2", className),
578
+ ...props
579
+ },
580
+ { shortcuts: root.shortcuts },
581
+ render
582
+ );
583
+ }
584
+ );
585
+ QuestionnaireChoices.displayName = "QuestionnaireChoices";
586
+ var QuestionnaireChoice = React__namespace.forwardRef(
587
+ ({ value, checked, defaultChecked, disabled = false, onChange, className, children, ...props }, ref) => {
588
+ const item = useItem("QuestionnaireChoice");
589
+ const type = item.multiple ? "checkbox" : "radio";
590
+ const isChecked = checked ?? (item.measured ? item.checked.includes(value) : !!defaultChecked);
591
+ const isDisabled = disabled || item.disabled;
592
+ const shortcut = disabled ? void 0 : item.shortcutFor(value);
593
+ return /* @__PURE__ */ jsxRuntime.jsxs(
594
+ "label",
595
+ {
596
+ ref,
597
+ "data-slot": "questionnaire-choice",
598
+ "data-questionnaire-choice-value": value,
599
+ "data-type": type,
600
+ "data-checked": flag(isChecked),
601
+ "data-unchecked": flag(!isChecked),
602
+ "data-disabled": flag(isDisabled),
603
+ "data-invalid": flag(item.invalid),
604
+ "data-shortcut": shortcut,
605
+ className: utils.cn(
606
+ "group/questionnaire-choice relative flex min-h-11 cursor-pointer select-none items-start gap-2.5 rounded-lg border border-input bg-transparent px-3 py-2.5 text-start text-sm outline-none transition-colors hover:bg-muted/50 has-[>input:focus-visible]:border-ring has-[>input:focus-visible]:ring-2 has-[>input:focus-visible]:ring-ring/50 data-[invalid]:border-destructive data-[checked]:border-primary/40 data-[checked]:bg-muted",
607
+ "data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
608
+ className
609
+ ),
610
+ ...props,
611
+ children: [
612
+ /* @__PURE__ */ jsxRuntime.jsx(
613
+ "input",
614
+ {
615
+ type,
616
+ name: item.name,
617
+ value,
618
+ checked,
619
+ defaultChecked: checked === void 0 ? defaultChecked : void 0,
620
+ disabled,
621
+ onChange,
622
+ "aria-invalid": item.invalid || void 0,
623
+ "aria-keyshortcuts": shortcut,
624
+ "data-questionnaire-control": "choice",
625
+ "data-slot": "questionnaire-choice-input",
626
+ className: "absolute inset-0 z-10 size-full cursor-pointer opacity-0"
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsxRuntime.jsx(
630
+ "span",
631
+ {
632
+ "aria-hidden": "true",
633
+ "data-slot": "questionnaire-choice-indicator",
634
+ className: utils.cn(
635
+ "pointer-events-none relative flex size-4 shrink-0 translate-y-[0.1125rem] items-center justify-center rounded-[4px] border border-input group-has-[[data-slot=questionnaire-choice-description]]/questionnaire-choice:translate-y-0.5 group-data-[checked]/questionnaire-choice:border-primary group-data-[checked]/questionnaire-choice:bg-primary group-data-[checked]/questionnaire-choice:text-primary-foreground",
636
+ type === "radio" && "rounded-full"
637
+ ),
638
+ children: type === "radio" ? /* @__PURE__ */ jsxRuntime.jsx(
639
+ "span",
640
+ {
641
+ "data-slot": "questionnaire-choice-indicator-dot",
642
+ className: "hidden size-2 rounded-full bg-primary-foreground group-data-[checked]/questionnaire-choice:block"
643
+ }
644
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
645
+ lucideReact.Check,
646
+ {
647
+ "data-slot": "questionnaire-choice-indicator-check",
648
+ className: "hidden size-3.5 group-data-[checked]/questionnaire-choice:block"
649
+ }
650
+ )
651
+ }
652
+ ),
653
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "data-slot": "questionnaire-choice-label", className: "flex min-w-0 flex-1 flex-col gap-0.5 leading-snug", children }),
654
+ shortcut ? /* @__PURE__ */ jsxRuntime.jsx(
655
+ "span",
656
+ {
657
+ "aria-hidden": "true",
658
+ "data-slot": "questionnaire-choice-shortcut",
659
+ className: "pointer-events-none ms-auto inline-flex size-5 shrink-0 translate-y-[0.1125rem] items-center justify-center rounded-md border border-input bg-background font-mono text-[0.625rem] font-medium leading-none text-muted-foreground group-has-[[data-slot=questionnaire-choice-description]]/questionnaire-choice:translate-y-0.5",
660
+ children: shortcut
661
+ }
662
+ ) : null
663
+ ]
664
+ }
665
+ );
666
+ }
667
+ );
668
+ QuestionnaireChoice.displayName = "QuestionnaireChoice";
669
+ var QuestionnaireChoiceDescription = React__namespace.forwardRef(
670
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
671
+ "span",
672
+ {
673
+ ref,
674
+ "data-slot": "questionnaire-choice-description",
675
+ className: utils.cn("text-muted-foreground", className),
676
+ ...props
677
+ }
678
+ )
679
+ );
680
+ QuestionnaireChoiceDescription.displayName = "QuestionnaireChoiceDescription";
681
+ var QuestionnaireInput = React__namespace.forwardRef(
682
+ ({ className, type = "text", disabled = false, render, ...props }, ref) => {
683
+ const item = useItem("QuestionnaireInput");
684
+ const initial = String(props.value ?? props.defaultValue ?? "");
685
+ const filled = item.measured ? item.text.trim() !== "" : initial.trim() !== "";
686
+ const isDisabled = disabled || item.disabled;
687
+ const state = { disabled: isDisabled, filled, invalid: item.invalid };
688
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "questionnaire-input-wrapper", className: "group/questionnaire-input relative w-full min-w-0", children: renderPart(
689
+ "input",
690
+ {
691
+ ref,
692
+ type,
693
+ name: filled ? item.name : void 0,
694
+ disabled,
695
+ "aria-invalid": item.invalid || void 0,
696
+ "data-questionnaire-control": "input",
697
+ "data-slot": "questionnaire-input",
698
+ "data-filled": flag(filled),
699
+ "data-empty": flag(!filled),
700
+ "data-disabled": flag(isDisabled),
701
+ "data-invalid": flag(item.invalid),
702
+ className: utils.cn(
703
+ "h-8 min-h-11 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-[color,box-shadow,background-color] focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-2 aria-[invalid=true]:ring-destructive/20 sm:min-h-0 md:text-sm",
704
+ "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
705
+ className
706
+ ),
707
+ ...props
708
+ },
709
+ state,
710
+ render
711
+ ) });
712
+ }
713
+ );
714
+ QuestionnaireInput.displayName = "QuestionnaireInput";
715
+ var QuestionnaireError = React__namespace.forwardRef(
716
+ ({ className, render, children, id, ...props }, ref) => {
717
+ const item = useItem("QuestionnaireError");
718
+ const { setHasError } = item;
719
+ React__namespace.useEffect(() => {
720
+ setHasError(true);
721
+ return () => setHasError(false);
722
+ }, [setHasError]);
723
+ const message = children ?? (item.required ? "Choose an answer to continue." : "Choose an answer or skip this question.");
724
+ return renderPart(
725
+ "p",
726
+ {
727
+ ref,
728
+ id: id ?? item.errorId,
729
+ role: item.invalid ? "alert" : void 0,
730
+ hidden: !item.invalid || void 0,
731
+ "data-slot": "questionnaire-error",
732
+ "data-invalid": flag(item.invalid),
733
+ className: utils.cn("mt-2 text-sm text-destructive [&[hidden]]:hidden", className),
734
+ ...props,
735
+ children: item.invalid ? message : null
736
+ },
737
+ { invalid: item.invalid },
738
+ render
739
+ );
740
+ }
741
+ );
742
+ QuestionnaireError.displayName = "QuestionnaireError";
743
+ var QuestionnaireActions = React__namespace.forwardRef(
744
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
745
+ "div",
746
+ {
747
+ ref,
748
+ "data-slot": "questionnaire-actions",
749
+ className: utils.cn(
750
+ "grid min-h-11 w-full grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-2 sm:min-h-8",
751
+ className
752
+ ),
753
+ ...props
754
+ }
755
+ )
756
+ );
757
+ QuestionnaireActions.displayName = "QuestionnaireActions";
758
+ var variantMap = {
759
+ default: "primary",
760
+ outline: "secondary",
761
+ primary: "primary",
762
+ cta: "cta",
763
+ secondary: "secondary",
764
+ ghost: "ghost",
765
+ destructive: "destructive",
766
+ link: "link"
767
+ };
768
+ var sizeMap = {
769
+ default: "md",
770
+ xs: "sm",
771
+ sm: "sm",
772
+ md: "md",
773
+ lg: "lg",
774
+ icon: "icon",
775
+ "icon-sm": "icon-sm",
776
+ "icon-lg": "icon-lg"
777
+ };
778
+ var actionLayout = {
779
+ previous: "col-start-1 row-start-1 justify-self-start",
780
+ skip: "col-start-2 row-start-1 justify-self-end",
781
+ next: "col-start-3 row-start-1 justify-self-end",
782
+ submit: "col-start-3 row-start-1 justify-self-end"
783
+ };
784
+ var actionLabel = {
785
+ previous: "Previous",
786
+ skip: "Skip",
787
+ next: "Next",
788
+ submit: "Submit"
789
+ };
790
+ function useAction(kind, { variant, size, render, disabled, onClick, className, children, ...props }, ref) {
791
+ const root = useRoot(`Questionnaire${actionLabel[kind]}`);
792
+ const last = root.total > 0 && root.index === root.total - 1;
793
+ const visible = root.active !== void 0 && (kind === "previous" ? root.index > 0 : kind === "skip" ? !root.isRequired(root.active) : kind === "next" ? !last : last);
794
+ const status = root.active !== void 0 ? root.statusOf(root.active) : "unanswered";
795
+ const shortcut = (kind === "next" || kind === "submit") && visible && !disabled ? "Enter" : void 0;
796
+ const state = { visible, disabled: !!disabled, shortcut, status };
797
+ const nucleuxVariant = variantMap[variant ?? (kind === "next" || kind === "submit" ? "default" : "outline")];
798
+ const nucleuxSize = sizeMap[size ?? "default"];
799
+ return renderPart(
800
+ "button",
801
+ {
802
+ ref,
803
+ type: kind === "submit" ? "submit" : "button",
804
+ disabled,
805
+ hidden: !visible || void 0,
806
+ "aria-keyshortcuts": shortcut ? "Control+Enter Meta+Enter" : void 0,
807
+ "data-slot": `questionnaire-${kind}`,
808
+ "data-size": size ?? "default",
809
+ "data-variant": variant ?? (kind === "next" || kind === "submit" ? "default" : "outline"),
810
+ "data-visible": flag(visible),
811
+ "data-hidden": flag(!visible),
812
+ "data-disabled": flag(disabled),
813
+ "data-shortcut": shortcut,
814
+ "data-status": status,
815
+ className: utils.cn(actionLayout[kind], "min-h-11 sm:min-h-0 data-[hidden]:hidden", className),
816
+ onClick: (event) => {
817
+ onClick?.(event);
818
+ if (event.defaultPrevented) return;
819
+ if (kind === "previous") root.previous();
820
+ else if (kind === "skip") root.skip();
821
+ else if (kind === "next") root.next();
822
+ },
823
+ ...props,
824
+ children: children ?? actionLabel[kind]
825
+ },
826
+ state,
827
+ render ?? /* @__PURE__ */ jsxRuntime.jsx(button.Button, { variant: nucleuxVariant, size: nucleuxSize })
828
+ );
829
+ }
830
+ var QuestionnairePrevious = React__namespace.forwardRef(
831
+ (props, ref) => useAction("previous", props, ref)
832
+ );
833
+ QuestionnairePrevious.displayName = "QuestionnairePrevious";
834
+ var QuestionnaireSkip = React__namespace.forwardRef(
835
+ (props, ref) => useAction("skip", props, ref)
836
+ );
837
+ QuestionnaireSkip.displayName = "QuestionnaireSkip";
838
+ var QuestionnaireNext = React__namespace.forwardRef(
839
+ (props, ref) => useAction("next", props, ref)
840
+ );
841
+ QuestionnaireNext.displayName = "QuestionnaireNext";
842
+ var QuestionnaireSubmit = React__namespace.forwardRef(
843
+ (props, ref) => useAction("submit", props, ref)
844
+ );
845
+ QuestionnaireSubmit.displayName = "QuestionnaireSubmit";
846
+
847
+ exports.Questionnaire = Questionnaire;
848
+ exports.QuestionnaireActions = QuestionnaireActions;
849
+ exports.QuestionnaireChoice = QuestionnaireChoice;
850
+ exports.QuestionnaireChoiceDescription = QuestionnaireChoiceDescription;
851
+ exports.QuestionnaireChoices = QuestionnaireChoices;
852
+ exports.QuestionnaireDescription = QuestionnaireDescription;
853
+ exports.QuestionnaireError = QuestionnaireError;
854
+ exports.QuestionnaireInput = QuestionnaireInput;
855
+ exports.QuestionnaireItem = QuestionnaireItem;
856
+ exports.QuestionnaireNext = QuestionnaireNext;
857
+ exports.QuestionnairePrevious = QuestionnairePrevious;
858
+ exports.QuestionnaireProgress = QuestionnaireProgress;
859
+ exports.QuestionnaireSkip = QuestionnaireSkip;
860
+ exports.QuestionnaireSubmit = QuestionnaireSubmit;
861
+ exports.QuestionnaireTitle = QuestionnaireTitle;
862
+ //# sourceMappingURL=index.cjs.map
863
+ //# sourceMappingURL=index.cjs.map