@esheet/builder 0.0.4-0 → 0.0.4-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/index.d.ts +16 -417
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +115 -115
  4. package/dist/lib/EsheetBuilder.d.ts +46 -0
  5. package/dist/lib/EsheetBuilder.d.ts.map +1 -0
  6. package/dist/lib/EsheetBuilder.js +111 -0
  7. package/dist/lib/builder-tools.d.ts +107 -0
  8. package/dist/lib/builder-tools.d.ts.map +1 -0
  9. package/dist/lib/builder-tools.js +321 -0
  10. package/dist/lib/components/BuilderHeader.d.ts +7 -0
  11. package/dist/lib/components/BuilderHeader.d.ts.map +1 -0
  12. package/dist/lib/components/BuilderHeader.js +390 -0
  13. package/dist/lib/components/Canvas.d.ts +12 -0
  14. package/dist/lib/components/Canvas.d.ts.map +1 -0
  15. package/dist/lib/components/Canvas.js +288 -0
  16. package/dist/lib/components/CodeView.d.ts +12 -0
  17. package/dist/lib/components/CodeView.d.ts.map +1 -0
  18. package/dist/lib/components/CodeView.js +197 -0
  19. package/dist/lib/components/FeedbackModal.d.ts +23 -0
  20. package/dist/lib/components/FeedbackModal.d.ts.map +1 -0
  21. package/dist/lib/components/FeedbackModal.js +22 -0
  22. package/dist/lib/components/FieldWrapper.d.ts +53 -0
  23. package/dist/lib/components/FieldWrapper.d.ts.map +1 -0
  24. package/dist/lib/components/FieldWrapper.js +152 -0
  25. package/dist/lib/components/MobileBottomDrawer.d.ts +9 -0
  26. package/dist/lib/components/MobileBottomDrawer.d.ts.map +1 -0
  27. package/dist/lib/components/MobileBottomDrawer.js +6 -0
  28. package/dist/lib/components/ToolPanel.d.ts +11 -0
  29. package/dist/lib/components/ToolPanel.d.ts.map +1 -0
  30. package/dist/lib/components/ToolPanel.js +117 -0
  31. package/dist/lib/components/edit-panel/CommonEditor.d.ts +16 -0
  32. package/dist/lib/components/edit-panel/CommonEditor.d.ts.map +1 -0
  33. package/dist/lib/components/edit-panel/CommonEditor.js +16 -0
  34. package/dist/lib/components/edit-panel/DraftIdEditor.d.ts +16 -0
  35. package/dist/lib/components/edit-panel/DraftIdEditor.d.ts.map +1 -0
  36. package/dist/lib/components/edit-panel/DraftIdEditor.js +32 -0
  37. package/dist/lib/components/edit-panel/EditPanel.d.ts +10 -0
  38. package/dist/lib/components/edit-panel/EditPanel.d.ts.map +1 -0
  39. package/dist/lib/components/edit-panel/EditPanel.js +156 -0
  40. package/dist/lib/components/edit-panel/InputTypeEditor.d.ts +16 -0
  41. package/dist/lib/components/edit-panel/InputTypeEditor.d.ts.map +1 -0
  42. package/dist/lib/components/edit-panel/InputTypeEditor.js +32 -0
  43. package/dist/lib/components/edit-panel/LogicEditor.d.ts +15 -0
  44. package/dist/lib/components/edit-panel/LogicEditor.d.ts.map +1 -0
  45. package/dist/lib/components/edit-panel/LogicEditor.js +392 -0
  46. package/dist/lib/components/edit-panel/MatrixEditor.d.ts +12 -0
  47. package/dist/lib/components/edit-panel/MatrixEditor.d.ts.map +1 -0
  48. package/dist/lib/components/edit-panel/MatrixEditor.js +32 -0
  49. package/dist/lib/components/edit-panel/OptionListEditor.d.ts +14 -0
  50. package/dist/lib/components/edit-panel/OptionListEditor.d.ts.map +1 -0
  51. package/dist/lib/components/edit-panel/OptionListEditor.js +27 -0
  52. package/dist/lib/hooks/useFormApi.d.ts +55 -0
  53. package/dist/lib/hooks/useFormApi.d.ts.map +1 -0
  54. package/dist/lib/hooks/useFormApi.js +80 -0
  55. package/dist/lib/hooks/useUiApi.d.ts +22 -0
  56. package/dist/lib/hooks/useUiApi.d.ts.map +1 -0
  57. package/dist/lib/hooks/useUiApi.js +37 -0
  58. package/dist/lib/hooks/useVisibleRootIds.d.ts +11 -0
  59. package/dist/lib/hooks/useVisibleRootIds.d.ts.map +1 -0
  60. package/dist/lib/hooks/useVisibleRootIds.js +32 -0
  61. package/dist/lib/icons.d.ts +31 -0
  62. package/dist/lib/icons.d.ts.map +1 -0
  63. package/dist/lib/icons.js +47 -0
  64. package/dist/lib/mcp/index.d.ts +7 -0
  65. package/dist/lib/mcp/index.d.ts.map +1 -0
  66. package/dist/lib/mcp/index.js +4 -0
  67. package/dist/lib/mcp/system-prompt.d.ts +2 -0
  68. package/dist/lib/mcp/system-prompt.d.ts.map +1 -0
  69. package/dist/lib/mcp/system-prompt.js +9 -0
  70. package/dist/lib/mcp/tool-definitions.d.ts +10 -0
  71. package/dist/lib/mcp/tool-definitions.d.ts.map +1 -0
  72. package/dist/lib/mcp/tool-definitions.js +436 -0
  73. package/dist/lib/mcp/tool-executor.d.ts +5 -0
  74. package/dist/lib/mcp/tool-executor.d.ts.map +1 -0
  75. package/dist/lib/mcp/tool-executor.js +482 -0
  76. package/dist/lib/mcp/useBuilderToolBridge.d.ts +31 -0
  77. package/dist/lib/mcp/useBuilderToolBridge.d.ts.map +1 -0
  78. package/dist/lib/mcp/useBuilderToolBridge.js +56 -0
  79. package/dist/lib/register-defaults.d.ts +2 -0
  80. package/dist/lib/register-defaults.d.ts.map +1 -0
  81. package/dist/lib/register-defaults.js +37 -0
  82. package/package.json +4 -4
@@ -0,0 +1,152 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { useFormApi } from '../hooks/useFormApi.js';
4
+ import { useUiApi } from '../hooks/useUiApi.js';
5
+ import { TrashIcon, ViewBigIcon, ViewSmallIcon, EditIcon, DragHandleIcon, } from '../icons.js';
6
+ /**
7
+ * FieldWrapper - Extensibility API for custom field components.
8
+ *
9
+ * Wraps a field with collapsible header, selection highlighting, edit/delete buttons, and drag handles.
10
+ * Exposes field data and tools to the render function, allowing users to create
11
+ * custom field types while getting all the built-in editor functionality.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <FieldWrapper fieldId={id} form={form} ui={ui}>
16
+ * {({ field, onUpdate, onRemove }) => (
17
+ * <div>
18
+ * <input
19
+ * value={field.question}
20
+ * onChange={(e) => onUpdate({ question: e.target.value })}
21
+ * />
22
+ * </div>
23
+ * )}
24
+ * </FieldWrapper>
25
+ * ```
26
+ */
27
+ export function FieldWrapper({ fieldId, form, ui, dragHandleRef, isSelectedOverride, onSelectOverride, selectedVariant = 'default', forceExpandVersion, forceCollapseVersion, children, }) {
28
+ const [isExpanded, setIsExpanded] = React.useState(() => {
29
+ const collapseActive = forceCollapseVersion !== undefined &&
30
+ (forceExpandVersion === undefined ||
31
+ forceCollapseVersion > forceExpandVersion);
32
+ return !collapseActive;
33
+ });
34
+ const lastForceExpandVersionRef = React.useRef(forceExpandVersion);
35
+ const lastForceCollapseVersionRef = React.useRef(forceCollapseVersion);
36
+ const { field, response, isVisible, isEnabled, isRequired, instanceId, field_, } = useFormApi(fieldId);
37
+ const { mode, selectedFieldId, selectField, setEditModalOpen } = useUiApi();
38
+ const isPreview = mode === 'preview';
39
+ const isSelected = !isPreview && (isSelectedOverride ?? selectedFieldId === fieldId);
40
+ const handleSelect = (e) => {
41
+ e.stopPropagation();
42
+ if (onSelectOverride) {
43
+ onSelectOverride(e);
44
+ return;
45
+ }
46
+ selectField(fieldId);
47
+ };
48
+ const handleToggleExpand = (e) => {
49
+ e.stopPropagation();
50
+ if (!isSelected)
51
+ handleSelect(e);
52
+ setIsExpanded((prev) => !prev);
53
+ };
54
+ React.useEffect(() => {
55
+ if (forceExpandVersion === undefined)
56
+ return;
57
+ if (lastForceExpandVersionRef.current === forceExpandVersion)
58
+ return;
59
+ lastForceExpandVersionRef.current = forceExpandVersion;
60
+ setIsExpanded(true);
61
+ }, [forceExpandVersion]);
62
+ React.useEffect(() => {
63
+ if (forceCollapseVersion === undefined)
64
+ return;
65
+ if (lastForceCollapseVersionRef.current === forceCollapseVersion)
66
+ return;
67
+ lastForceCollapseVersionRef.current = forceCollapseVersion;
68
+ setIsExpanded(false);
69
+ }, [forceCollapseVersion]);
70
+ if (!field) {
71
+ return null;
72
+ }
73
+ // In preview mode, hide fields whose visibility rules evaluate to false.
74
+ if (isPreview && !isVisible) {
75
+ return null;
76
+ }
77
+ // --- Preview mode: minimal chrome, no builder controls ---
78
+ if (isPreview) {
79
+ const isSection = field.definition.fieldType === 'section';
80
+ const parentNode = field.parentId
81
+ ? form.getState().getField(field.parentId)
82
+ : null;
83
+ const isChildOfSection = parentNode?.definition.fieldType === 'section';
84
+ return (_jsx("div", { className: `field-wrapper ms:bg-mssurface${isSection ? ' ms:mb-1 ms:border ms:border-msborder ms:rounded' : ''}${!isSection && !isChildOfSection
85
+ ? ' ms:mb-1 ms:p-6 ms:border ms:border-msborder ms:rounded'
86
+ : ''}${isChildOfSection
87
+ ? ' ms:p-6 ms:border-b ms:border-msborder ms:last:border-b-0'
88
+ : ''}${!isEnabled ? ' ms:opacity-50 ms:pointer-events-none' : ''}`, "aria-disabled": !isEnabled || undefined, children: children({
89
+ field,
90
+ form,
91
+ ui,
92
+ isSelected: false,
93
+ isPreview: true,
94
+ isEnabled,
95
+ isRequired,
96
+ response,
97
+ onRemove: field_.remove,
98
+ onUpdate: field_.update,
99
+ onResponse: field_.setResponse,
100
+ }) }));
101
+ }
102
+ // --- Build/Code mode: collapsible with full editor chrome ---
103
+ const questionText = field.definition.fieldType === 'section'
104
+ ? field.definition.title || ''
105
+ : field.definition.question || '';
106
+ const questionPreview = questionText
107
+ ? questionText.length > 18
108
+ ? `${questionText.slice(0, 18)}...`
109
+ : questionText
110
+ : 'Untitled';
111
+ // While collapsed, keep the wrapper compact.
112
+ const effectiveExpanded = isExpanded;
113
+ // Base wrapper classes
114
+ let wrapperClass = isSelected
115
+ ? 'field-wrapper ms:group ms:relative ms:bg-mssurface ms:border-2 ms:border-msprimary ms:rounded-lg ms:transition-all ms:outline-none'
116
+ : 'field-wrapper ms:group ms:relative ms:bg-mssurface ms:border ms:border-msborder ms:rounded-lg ms:transition-all ms:hover:border-msprimary/30 ms:outline-none';
117
+ if (!effectiveExpanded) {
118
+ wrapperClass += ' ms:p-0';
119
+ }
120
+ else {
121
+ wrapperClass += ' ms:p-6';
122
+ }
123
+ // Header padding/margin adjustments
124
+ const headerClass = effectiveExpanded
125
+ ? 'field-wrapper-edit-header ms:flex ms:justify-between ms:items-center ms:gap-3 ms:px-3 ms:py-2.5 ms:-mx-6 ms:-mt-6 ms:mb-4 ms:bg-msbackgroundsecondary ms:border-b ms:border-msborder ms:rounded-t-lg'
126
+ : 'field-wrapper-edit-header ms:flex ms:justify-between ms:items-center ms:gap-3 ms:px-3 ms:py-2.5 ms:m-0 ms:bg-msbackgroundsecondary ms:border-b ms:border-msborder ms:rounded-lg';
127
+ return (_jsxs("div", { className: wrapperClass, onClick: handleSelect, "aria-selected": isSelected || undefined, tabIndex: -1, children: [_jsxs("div", { className: headerClass, children: [dragHandleRef !== undefined && (_jsx("div", { ref: dragHandleRef, className: "drag-handle ms:flex ms:items-center ms:p-1 ms:text-mstextmuted ms:cursor-grab ms:active:cursor-grabbing ms:shrink-0 ms:user-select-none", style: { touchAction: 'none' }, "aria-label": "Drag to reorder", children: _jsx(DragHandleIcon, { className: "ms:w-4 ms:h-4" }) })), _jsxs("div", { className: "ms:flex-1 ms:flex ms:items-center ms:gap-1.5 ms:min-w-0 ms:select-none", children: [_jsx("span", { className: "fieldtype-chip ms:inline-block ms:shrink-0 ms:text-xs ms:font-medium ms:text-msprimary ms:bg-msprimary/10 ms:px-2 ms:py-0.5 ms:rounded", children: field.definition.fieldType }), _jsxs("span", { className: "id-chip ms:inline-flex ms:items-center ms:gap-1 ms:min-w-0 ms:shrink ms:text-xs ms:font-mono ms:text-mssecondary ms:bg-mssecondary/10 ms:px-2 ms:py-0.5 ms:rounded", children: [_jsx("span", { className: "ms:opacity-70 ms:shrink-0", children: "id:" }), _jsx("span", { className: "ms:font-semibold ms:truncate", children: field.definition.id })] }), _jsx("span", { className: "question-label ms:text-xs ms:text-mstextmuted ms:truncate ms:min-w-0", children: questionPreview }), field.definition.required && (_jsx("span", { className: "required-indicator ms:text-msdanger ms:text-xs ms:font-bold ms:shrink-0", "aria-label": "Required", children: "*" }))] }), _jsxs("div", { className: "field-wrapper-actions ms:flex ms:items-center ms:gap-1 ms:shrink-0", children: [_jsx("button", { type: "button", onClick: (e) => {
128
+ e.stopPropagation();
129
+ if (onSelectOverride) {
130
+ onSelectOverride(e);
131
+ }
132
+ else {
133
+ selectField(fieldId);
134
+ }
135
+ setEditModalOpen(true);
136
+ }, className: "field-edit-btn ms:block ms:lg:hidden ms:p-1.5 ms:bg-transparent ms:text-mstextmuted ms:hover:bg-msbackgroundhover ms:rounded ms:transition-colors ms:border-0 ms:outline-none ms:focus:outline-none", title: "Edit", "aria-label": "Edit field", children: _jsx(EditIcon, { className: "ms:h-5 ms:w-5 ms:text-mstextmuted" }) }), _jsx("button", { type: "button", onClick: handleToggleExpand, "aria-expanded": effectiveExpanded, "aria-controls": `${instanceId}-fw-body-${fieldId}`, title: effectiveExpanded ? 'Collapse' : 'Expand', "aria-label": effectiveExpanded ? 'Collapse field' : 'Expand field', className: "field-collapse-btn ms:p-1.5 ms:bg-transparent ms:text-mstextmuted ms:hover:bg-msbackgroundhover ms:rounded ms:transition-colors ms:border-0 ms:outline-none ms:focus:outline-none", children: effectiveExpanded ? (_jsx(ViewSmallIcon, { className: "ms:collapse-icon ms:h-5 ms:w-5 ms:text-mstextmuted" })) : (_jsx(ViewBigIcon, { className: "ms:collapse-icon ms:h-5 ms:w-5 ms:text-mstextmuted" })) }), _jsx("button", { type: "button", onClick: (e) => {
137
+ e.stopPropagation();
138
+ field_.remove();
139
+ }, className: "field-delete-btn ms:p-1.5 ms:bg-transparent ms:text-mstextmuted ms:hover:bg-msdanger/10 ms:hover:text-msdanger ms:rounded ms:transition-colors ms:border-0 ms:outline-none ms:focus:outline-none", title: "Delete", "aria-label": "Delete field", children: _jsx(TrashIcon, { className: "ms:h-5 ms:w-5" }) })] })] }), _jsx("div", { id: `${instanceId}-fw-body-${fieldId}`, className: "field-wrapper-body", hidden: !effectiveExpanded || undefined, children: children({
140
+ field,
141
+ form,
142
+ ui,
143
+ isSelected,
144
+ isPreview: false,
145
+ isEnabled,
146
+ isRequired,
147
+ response,
148
+ onRemove: field_.remove,
149
+ onUpdate: field_.update,
150
+ onResponse: field_.setResponse,
151
+ }) })] }));
152
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export interface MobileBottomDrawerProps {
3
+ title: string;
4
+ open: boolean;
5
+ onClose: () => void;
6
+ children: React.ReactNode;
7
+ }
8
+ export declare function MobileBottomDrawer({ title, open, onClose, children, }: MobileBottomDrawerProps): import("react/jsx-runtime").JSX.Element | null;
9
+ //# sourceMappingURL=MobileBottomDrawer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MobileBottomDrawer.d.ts","sourceRoot":"","sources":["../../../src/lib/components/MobileBottomDrawer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,GACT,EAAE,uBAAuB,kDA+BzB"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ export function MobileBottomDrawer({ title, open, onClose, children, }) {
3
+ if (!open)
4
+ return null;
5
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "ms:lg:hidden ms:fixed ms:inset-0 ms:z-40 ms:bg-msoverlay ms:border-0", onClick: onClose, "aria-label": `Close ${title} drawer` }), _jsxs("div", { className: "ms:lg:hidden ms:fixed ms:left-0 ms:right-0 ms:bottom-0 ms:z-50 ms:h-[50dvh] ms:bg-mssurface ms:border-t ms:border-msborder ms:rounded-t-2xl ms:shadow-2xl ms:overflow-hidden", children: [_jsxs("div", { className: "ms:flex ms:items-center ms:justify-between ms:px-4 ms:py-2 ms:border-b ms:border-msborder", children: [_jsx("span", { className: "ms:text-sm ms:font-medium ms:text-mstext", children: title }), _jsx("button", { type: "button", onClick: onClose, className: "ms:px-2 ms:py-1 ms:bg-transparent ms:text-mstextmuted ms:border-0 ms:outline-none ms:focus:outline-none", "aria-label": `Close ${title} drawer`, children: "Close" })] }), _jsx("div", { className: "ms:h-[calc(50dvh-45px)] ms:overflow-y-auto", children: children })] })] }));
6
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ export interface ToolPanelProps {
3
+ }
4
+ /**
5
+ * ToolPanel - Left panel listing available field types.
6
+ *
7
+ * Clicking a button calls `form.addField(type)` and auto-selects
8
+ * the new field. Groups field types by category from the registry.
9
+ */
10
+ export declare const ToolPanel: React.NamedExoticComponent<ToolPanelProps>;
11
+ //# sourceMappingURL=ToolPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolPanel.d.ts","sourceRoot":"","sources":["../../../src/lib/components/ToolPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAmB1B,MAAM,WAAW,cAAc;CAAG;AA2ClC;;;;;GAKG;AACH,eAAO,MAAM,SAAS,4CAuLpB,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { getRegisteredFieldTypes, getFieldTypeMeta, } from '@esheet/core';
4
+ import { TextFieldsIcon, SelectionFieldsIcon, RatingIcon, MatrixIcon, RichContentIcon, OrganizationIcon, ChevronIcon, } from '../icons.js';
5
+ import { useFormApi } from '../hooks/useFormApi.js';
6
+ import { useUiApi } from '../hooks/useUiApi.js';
7
+ /** Category display labels. */
8
+ const CATEGORY_LABELS = {
9
+ text: 'Text Fields',
10
+ selection: 'Selection Fields',
11
+ rating: 'Rating & Ranking',
12
+ matrix: 'Matrix Fields',
13
+ rich: 'Rich Content',
14
+ organization: 'Organization',
15
+ };
16
+ /** Category icons mapped by display label. */
17
+ const CATEGORY_ICONS = {
18
+ 'Text Fields': TextFieldsIcon,
19
+ 'Selection Fields': SelectionFieldsIcon,
20
+ 'Rating & Ranking': RatingIcon,
21
+ 'Matrix Fields': MatrixIcon,
22
+ 'Rich Content': RichContentIcon,
23
+ Organization: OrganizationIcon,
24
+ };
25
+ import { getFieldComponent } from '@esheet/fields';
26
+ /** Build category → field type[] map from the registry. Only includes types with a registered React component. */
27
+ function buildCategories() {
28
+ const result = {};
29
+ for (const type of getRegisteredFieldTypes()) {
30
+ if (!getFieldComponent(type))
31
+ continue;
32
+ const meta = getFieldTypeMeta(type);
33
+ if (!meta)
34
+ continue;
35
+ const cat = meta.category ?? 'other';
36
+ const label = CATEGORY_LABELS[cat] ?? 'Other';
37
+ if (!result[label])
38
+ result[label] = [];
39
+ result[label].push({ type, label: meta.label });
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * ToolPanel - Left panel listing available field types.
45
+ *
46
+ * Clicking a button calls `form.addField(type)` and auto-selects
47
+ * the new field. Groups field types by category from the registry.
48
+ */
49
+ export const ToolPanel = React.memo(function ToolPanel(_props) {
50
+ const { normalized, form: formApi, _form } = useFormApi();
51
+ const { selectedFieldId, selectField, selectFieldChild, _ui } = useUiApi();
52
+ const selectedField = selectedFieldId
53
+ ? normalized.byId[selectedFieldId]
54
+ : undefined;
55
+ const selectedSectionId = selectedField?.definition.fieldType === 'section'
56
+ ? selectedFieldId
57
+ : undefined;
58
+ const selectedSectionLabel = selectedSectionId
59
+ ? selectedField?.definition?.title ||
60
+ selectedField?.definition.id ||
61
+ selectedSectionId
62
+ : null;
63
+ const categories = React.useMemo(buildCategories, []);
64
+ const categoryNames = React.useMemo(() => Object.keys(categories), [categories]);
65
+ const orderedCategoryNames = React.useMemo(() => {
66
+ const org = categoryNames.includes('Organization') ? ['Organization'] : [];
67
+ const rest = categoryNames.filter((name) => name !== 'Organization');
68
+ return [...org, ...rest];
69
+ }, [categoryNames]);
70
+ const [collapsed, setCollapsed] = React.useState(() => new Set());
71
+ const toggleCategory = React.useCallback((name) => {
72
+ setCollapsed((prev) => {
73
+ const next = new Set(prev);
74
+ if (next.has(name))
75
+ next.delete(name);
76
+ else
77
+ next.add(name);
78
+ return next;
79
+ });
80
+ }, []);
81
+ const toggleAll = React.useCallback(() => {
82
+ setCollapsed((prev) => prev.size === categoryNames.length ? new Set() : new Set(categoryNames));
83
+ }, [categoryNames]);
84
+ const handleAdd = React.useCallback((type) => {
85
+ const curSelectedId = _ui.getState().selectedFieldId;
86
+ const curSelectedField = curSelectedId
87
+ ? _form.getState().getField(curSelectedId)
88
+ : undefined;
89
+ const sectionParentId = curSelectedField?.definition.fieldType === 'section'
90
+ ? curSelectedId
91
+ : undefined;
92
+ const newId = formApi.addField(type, sectionParentId ? { parentId: sectionParentId } : undefined);
93
+ if (newId) {
94
+ if (sectionParentId) {
95
+ selectFieldChild(sectionParentId, newId);
96
+ }
97
+ else {
98
+ selectField(newId);
99
+ }
100
+ }
101
+ }, [_form, _ui, formApi, selectField, selectFieldChild]);
102
+ const allCollapsed = collapsed.size === categoryNames.length;
103
+ return (_jsxs("div", { className: "tool-panel ms:flex ms:flex-1 ms:flex-col ms:min-h-0", children: [_jsxs("h3", { className: "tool-panel-title ms:sticky ms:top-0 ms:z-10 ms:bg-mssurface ms:text-sm ms:font-semibold ms:text-mstext ms:py-4 ms:px-4 ms:border-b ms:border-msborder ms:flex ms:items-center ms:justify-between", children: [_jsxs("div", { className: "ms:flex ms:min-w-0 ms:items-center ms:gap-2", children: [_jsx("span", { children: "Tools" }), _jsx("span", { className: `ms:inline-flex ms:min-w-0 ms:max-w-[120px] ms:flex-shrink ms:items-center ms:gap-1 ms:rounded-full ms:px-2.5 ms:py-1 ms:text-[11px] ms:font-medium ${selectedSectionId
104
+ ? 'ms:bg-msprimary/10 ms:text-msprimary'
105
+ : 'ms:bg-msbackgroundsecondary ms:text-mstextmuted'}`, title: selectedSectionId
106
+ ? `Adding into section: ${selectedSectionLabel ?? ''}`
107
+ : 'Adding into root', children: selectedSectionId ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => selectField(null), className: "ms:flex ms:min-w-0 ms:flex-1 ms:items-center ms:gap-1 ms:bg-transparent ms:p-0 ms:text-left ms:text-msprimary ms:outline-none ms:focus:outline-none ms:cursor-pointer", title: "Switch to adding into root", "aria-label": "Switch to adding into root", children: [_jsx("span", { className: "ms:inline-flex ms:h-1.5 ms:w-1.5 ms:rounded-full ms:bg-msprimary" }), _jsx("span", { className: "ms:min-w-0 ms:flex-shrink ms:truncate", children: (selectedSectionLabel ?? '').length > 12
108
+ ? `${(selectedSectionLabel ?? '').slice(0, 12)}…`
109
+ : selectedSectionLabel })] }), _jsx("button", { type: "button", onClick: () => selectField(null), className: "ms:inline-flex ms:h-4 ms:w-4 ms:flex-shrink-0 ms:items-center ms:justify-center ms:rounded-full ms:bg-transparent ms:text-msprimary ms:hover:bg-msprimary/15 ms:outline-none ms:focus:outline-none ms:cursor-pointer", title: "Switch to adding into root", "aria-label": "Switch to adding into root", children: "\u00D7" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "ms:inline-flex ms:h-1.5 ms:w-1.5 ms:rounded-full ms:bg-mstextmuted" }), _jsx("span", { className: "ms:min-w-0 ms:flex-shrink ms:truncate", children: "Adding into root" })] })) })] }), _jsx("button", { type: "button", onClick: toggleAll, className: "toggle-all-btn ms:flex-shrink-0 ms:text-xs ms:font-normal ms:text-mstextmuted ms:hover:text-mstext ms:bg-transparent ms:border-0 ms:outline-none ms:focus:outline-none ms:cursor-pointer ms:transition-colors", title: allCollapsed ? 'Expand all' : 'Collapse all', children: allCollapsed ? 'Expand all' : 'Collapse all' })] }), _jsx("div", { className: "tool-panel-body ms:flex-1 ms:min-h-0 ms:overflow-y-auto", children: orderedCategoryNames.map((categoryName) => {
110
+ const items = categories[categoryName] ?? [];
111
+ const isCollapsed = collapsed.has(categoryName);
112
+ return (_jsxs("div", { className: "tool-category", children: [_jsxs("button", { type: "button", onClick: () => toggleCategory(categoryName), className: "tool-category-title ms:w-full ms:sticky ms:top-0 ms:z-[5] ms:bg-mssurface ms:text-xs ms:font-semibold ms:text-mstextmuted ms:px-4 ms:py-2.5 ms:border-b ms:border-msborder ms:border-0 ms:uppercase ms:tracking-wide ms:flex ms:items-center ms:gap-1.5 ms:cursor-pointer ms:hover:bg-msbackgroundhover ms:transition-colors ms:outline-none ms:focus:outline-none", "aria-expanded": !isCollapsed, children: [_jsx(ChevronIcon, { className: `ms:w-3.5 ms:h-3.5 ms:shrink-0 ms:transition-transform ${isCollapsed ? 'ms:-rotate-90' : ''}` }), CATEGORY_ICONS[categoryName] &&
113
+ React.createElement(CATEGORY_ICONS[categoryName], {
114
+ className: 'ms:w-3.5 ms:h-3.5 ms:shrink-0',
115
+ }), _jsx("span", { className: "ms:flex-1 ms:text-left", children: categoryName })] }), !isCollapsed && (_jsx("div", { className: "tool-items ms:grid ms:grid-cols-1 ms:gap-1.5 ms:px-3 ms:py-2", children: items.map(({ type, label }) => (_jsxs("button", { type: "button", className: "tool-btn ms:flex ms:items-center ms:gap-2 ms:px-3 ms:py-2 ms:text-sm ms:text-left ms:rounded-md ms:bg-msbackground ms:text-mstext ms:border ms:border-transparent ms:transition-colors ms:hover:bg-msprimary/10 ms:hover:border-msprimary/40 ms:hover:text-msprimary ms:cursor-pointer ms:outline-none ms:focus:outline-none ms:focus-visible:ring-2 ms:focus-visible:ring-msprimary", onClick: () => handleAdd(type), title: `Add ${label}`, children: [_jsx("span", { className: "tool-btn-plus ms:flex ms:items-center ms:justify-center ms:w-5 ms:h-5 ms:rounded ms:bg-msbackgroundsecondary ms:text-mstextmuted ms:text-xs ms:font-bold ms:shrink-0", children: "+" }), label] }, type))) }))] }, categoryName));
116
+ }) })] }));
117
+ });
@@ -0,0 +1,16 @@
1
+ import type { FieldDefinition } from '@esheet/core';
2
+ export interface CommonEditorProps {
3
+ fieldId: string;
4
+ def: Omit<FieldDefinition, 'fields'>;
5
+ onUpdate: (patch: Partial<Omit<FieldDefinition, 'fields'>>) => void;
6
+ /** Called to rename the field ID. Returns false if the name collides. */
7
+ onRenameId: (newId: string) => boolean;
8
+ }
9
+ /**
10
+ * CommonEditor — shared property editors for all non-section fields.
11
+ *
12
+ * Renders: ID, Question, Sublabel (description), Required toggle,
13
+ * and InputTypeEditor for text/longtext fields.
14
+ */
15
+ export declare function CommonEditor({ fieldId, def, onUpdate, onRenameId, }: CommonEditorProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=CommonEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommonEditor.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/edit-panel/CommonEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,cAAc,CAAC;AAMnE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC,KAAK,IAAI,CAAC;IACpE,yEAAyE;IACzE,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACxC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,GAAG,EACH,QAAQ,EACR,UAAU,GACX,EAAE,iBAAiB,2CAmEnB"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CustomCheckbox } from '@esheet/fields';
3
+ import { useInstanceId } from '../../EsheetBuilder.js';
4
+ import { DraftIdEditor } from './DraftIdEditor.js';
5
+ import { InputTypeEditor } from './InputTypeEditor.js';
6
+ /**
7
+ * CommonEditor — shared property editors for all non-section fields.
8
+ *
9
+ * Renders: ID, Question, Sublabel (description), Required toggle,
10
+ * and InputTypeEditor for text/longtext fields.
11
+ */
12
+ export function CommonEditor({ fieldId, def, onUpdate, onRenameId, }) {
13
+ const instanceId = useInstanceId();
14
+ const showInputType = def.fieldType === 'text' || def.fieldType === 'longtext';
15
+ return (_jsxs("div", { className: "common-editor ms:space-y-3", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: `${instanceId}-editor-id-${fieldId}`, className: "edit-label ms:block ms:text-sm ms:font-medium ms:text-mstext ms:mb-1", children: "Field ID" }), _jsx(DraftIdEditor, { id: def.id, fieldId: fieldId, onCommit: onRenameId })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: `${instanceId}-editor-question-${fieldId}`, className: "edit-label ms:block ms:text-sm ms:text-mstext ms:mb-1", children: "Label / Question" }), _jsx("input", { id: `${instanceId}-editor-question-${fieldId}`, type: "text", value: def.question ?? '', onChange: (e) => onUpdate({ question: e.currentTarget.value }), placeholder: "Enter question text", className: "ms:w-full ms:min-w-0 ms:px-3 ms:py-2 ms:text-sm ms:bg-mssurface ms:border ms:border-msborder ms:rounded ms:text-mstext ms:placeholder:text-mstextmuted ms:focus:outline-none ms:focus:ring-1 ms:focus:ring-msprimary ms:focus:border-msprimary ms:transition-colors" })] }), _jsxs("div", { className: "required-toggle ms:flex ms:items-center ms:gap-2 ms:text-sm ms:text-mstext", children: [_jsx(CustomCheckbox, { id: `${instanceId}-editor-required-${fieldId}`, checked: def.required ?? false, onChange: (checked) => onUpdate({ required: checked }), size: "sm" }), _jsx("label", { htmlFor: `${instanceId}-editor-required-${fieldId}`, className: "ms:cursor-pointer ms:select-none", children: "Required" })] }), showInputType && (_jsx(InputTypeEditor, { fieldId: fieldId, inputType: def.inputType ?? 'string', unit: def.unit, onChange: (patch) => onUpdate(patch) }))] }));
16
+ }
@@ -0,0 +1,16 @@
1
+ export interface DraftIdEditorProps {
2
+ /** Current persisted ID. */
3
+ id: string;
4
+ /** Parent field ID for prefixing the DOM id attribute. */
5
+ fieldId: string;
6
+ /** Called with the new ID when the user commits (blur / Enter). Returns false if invalid. */
7
+ onCommit: (newId: string) => boolean;
8
+ }
9
+ /**
10
+ * DraftIdEditor — edits a field ID with commit-on-blur/Enter.
11
+ *
12
+ * Keeps a local draft so keystrokes don't immediately rename the field.
13
+ * Reverts to the persisted `id` if the commit callback rejects.
14
+ */
15
+ export declare function DraftIdEditor({ id, fieldId, onCommit }: DraftIdEditorProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=DraftIdEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DraftIdEditor.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/edit-panel/DraftIdEditor.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,4BAA4B;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,kBAAkB,2CAmC1E"}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { useInstanceId } from '../../EsheetBuilder.js';
4
+ /**
5
+ * DraftIdEditor — edits a field ID with commit-on-blur/Enter.
6
+ *
7
+ * Keeps a local draft so keystrokes don't immediately rename the field.
8
+ * Reverts to the persisted `id` if the commit callback rejects.
9
+ */
10
+ export function DraftIdEditor({ id, fieldId, onCommit }) {
11
+ const instanceId = useInstanceId();
12
+ const [draft, setDraft] = React.useState(id);
13
+ // Sync draft when the external id changes (e.g. another editor renamed it).
14
+ React.useEffect(() => {
15
+ setDraft(id);
16
+ }, [id]);
17
+ const commit = () => {
18
+ const trimmed = draft.trim();
19
+ if (trimmed === id)
20
+ return; // no change
21
+ if (!trimmed || !onCommit(trimmed)) {
22
+ setDraft(id); // revert on rejection
23
+ }
24
+ };
25
+ const handleKeyDown = (e) => {
26
+ if (e.key === 'Enter') {
27
+ e.preventDefault();
28
+ e.target.blur();
29
+ }
30
+ };
31
+ return (_jsx("input", { id: `${instanceId}-editor-id-${fieldId}`, type: "text", value: draft, onChange: (e) => setDraft(e.currentTarget.value), onBlur: commit, onKeyDown: handleKeyDown, className: "ms:w-full ms:min-w-0 ms:px-2 ms:py-1 ms:text-sm ms:bg-transparent ms:border ms:border-msborder ms:rounded ms:text-mstext ms:font-mono ms:placeholder:text-mstextmuted ms:focus:outline-none ms:focus:ring-2 ms:focus:ring-msprimary ms:focus:border-msprimary" }));
32
+ }
@@ -0,0 +1,10 @@
1
+ export interface EditPanelProps {
2
+ }
3
+ /**
4
+ * EditPanel — right panel for editing the selected field's properties.
5
+ *
6
+ * Shows Edit tab (common + per-type editors) and Logic tab.
7
+ * Renders nothing meaningful when no field is selected.
8
+ */
9
+ export declare function EditPanel(_props: EditPanelProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=EditPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditPanel.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/edit-panel/EditPanel.tsx"],"names":[],"mappings":"AAoBA,MAAM,WAAW,cAAc;CAAG;AAElC;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,2CAmI/C"}
@@ -0,0 +1,156 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { getFieldTypeMeta, } from '@esheet/core';
4
+ import { useInstanceId } from '../../EsheetBuilder.js';
5
+ import { EditIcon, LogicIcon } from '../../icons.js';
6
+ import { DraftIdEditor } from './DraftIdEditor.js';
7
+ import { CommonEditor } from './CommonEditor.js';
8
+ import { OptionListEditor } from './OptionListEditor.js';
9
+ import { MatrixEditor } from './MatrixEditor.js';
10
+ import { LogicEditor } from './LogicEditor.js';
11
+ import { useFormApi } from '../../hooks/useFormApi.js';
12
+ import { useUiApi } from '../../hooks/useUiApi.js';
13
+ /**
14
+ * EditPanel — right panel for editing the selected field's properties.
15
+ *
16
+ * Shows Edit tab (common + per-type editors) and Logic tab.
17
+ * Renders nothing meaningful when no field is selected.
18
+ */
19
+ export function EditPanel(_props) {
20
+ const { selectedFieldId, selectedFieldChildId, editTab, selectField, setEditTab, } = useUiApi();
21
+ const { normalized } = useFormApi(undefined);
22
+ // Bind field actions to the currently selected field
23
+ const { field_: selectedField_ } = useFormApi(selectedFieldId ?? undefined);
24
+ // Logic tab target: when a section is selected, edit logic for the active child.
25
+ const logicField = React.useMemo(() => {
26
+ if (!selectedFieldId)
27
+ return undefined;
28
+ const parent = normalized.byId[selectedFieldId];
29
+ if (!parent)
30
+ return undefined;
31
+ if (parent.definition.fieldType !== 'section' || !selectedFieldChildId) {
32
+ return parent;
33
+ }
34
+ return normalized.byId[selectedFieldChildId] ?? parent;
35
+ }, [normalized, selectedFieldId, selectedFieldChildId]);
36
+ const activeField = selectedFieldId
37
+ ? normalized.byId[selectedFieldId]
38
+ : undefined;
39
+ // No selection
40
+ if (!selectedFieldId || !activeField) {
41
+ return (_jsx("div", { className: "edit-panel-empty ms:flex ms:flex-1 ms:min-h-0 ms:items-center ms:justify-center ms:text-mstextmuted ms:text-sm ms:p-4 ms:text-center", children: "Select a field to edit its properties" }));
42
+ }
43
+ const def = activeField.definition;
44
+ const meta = getFieldTypeMeta(def.fieldType);
45
+ const logicTargetLabel = logicField
46
+ ? logicField.definition.fieldType === 'section'
47
+ ? logicField.definition.title || logicField.definition.id
48
+ : logicField.definition.question || logicField.definition.id
49
+ : '';
50
+ const logicTargetQuestion = logicField
51
+ ? logicField.definition.fieldType === 'section'
52
+ ? logicField.definition.title || ''
53
+ : logicField.definition.question || ''
54
+ : '';
55
+ const logicTargetQuestionShort = logicTargetQuestion
56
+ ? logicTargetQuestion.length > 18
57
+ ? `${logicTargetQuestion.slice(0, 18)}...`
58
+ : logicTargetQuestion
59
+ : logicTargetLabel;
60
+ const handleUpdate = (patch) => {
61
+ selectedField_.update(patch);
62
+ };
63
+ const handleRenameId = (newId) => {
64
+ const success = selectedField_.update({ id: newId });
65
+ if (success) {
66
+ selectField(newId);
67
+ }
68
+ return success;
69
+ };
70
+ const setTab = (tab) => setEditTab(tab);
71
+ return (_jsxs("div", { className: "edit-panel ms:flex ms:flex-1 ms:flex-col ms:min-h-0", children: [_jsx("div", { className: "edit-panel-tabs ms:sticky ms:top-0 ms:z-10 ms:bg-mssurface ms:border-b ms:border-msborder ms:px-4 ms:py-2.5 ms:shrink-0", children: _jsxs("div", { className: "ms:flex ms:gap-1 ms:rounded-lg ms:border ms:border-msborder ms:bg-msbackground ms:p-1", children: [_jsxs("button", { type: "button", onClick: () => setTab('edit'), className: `edit-tab-btn ms:flex-1 ms:flex ms:items-center ms:justify-center ms:gap-1.5 ms:px-3 ms:py-1.5 ms:rounded-md ms:text-xs ms:font-medium ms:transition-colors ms:border-0 ms:outline-none ms:focus:outline-none ms:cursor-pointer ${editTab === 'edit'
72
+ ? 'ms:bg-msprimary ms:text-mstextsecondary ms:shadow-sm'
73
+ : 'ms:bg-transparent ms:text-mstextmuted ms:hover:text-mstext ms:hover:bg-mssurface'}`, children: [_jsx(EditIcon, { className: "ms:w-3.5 ms:h-3.5" }), _jsx("span", { children: "Edit" })] }), _jsxs("button", { type: "button", onClick: () => setTab('logic'), className: `logic-tab-btn ms:flex-1 ms:flex ms:items-center ms:justify-center ms:gap-1.5 ms:px-3 ms:py-1.5 ms:rounded-md ms:text-xs ms:font-medium ms:transition-colors ms:border-0 ms:outline-none ms:focus:outline-none ms:cursor-pointer ${editTab === 'logic'
74
+ ? 'ms:bg-msprimary ms:text-mstextsecondary ms:shadow-sm'
75
+ : 'ms:bg-transparent ms:text-mstextmuted ms:hover:text-mstext ms:hover:bg-mssurface'}`, children: [_jsx(LogicIcon, { className: "ms:w-3.5 ms:h-3.5" }), _jsx("span", { children: "Logic" })] })] }) }), _jsx("div", { className: "edit-panel-content ms:flex-1 ms:min-h-0 ms:p-4", children: editTab === 'edit' ? (_jsx(EditTabContent, { fieldId: selectedFieldId, def: def, meta: meta, onUpdate: handleUpdate, onRenameId: handleRenameId })) : logicField ? (_jsxs("div", { className: "ms:space-y-2", children: [_jsxs("div", { className: "ms:flex ms:flex-wrap ms:items-center ms:gap-1.5 ms:text-xs ms:text-mstextmuted ms:bg-msbackground ms:border ms:border-msborder ms:rounded ms:px-2.5 ms:py-1.5", children: [_jsx("span", { className: "ms:inline-block ms:text-xs ms:font-medium ms:text-msprimary ms:bg-msprimary/10 ms:px-2 ms:py-0.5 ms:rounded ms:shrink-0", children: logicField.definition.fieldType }), _jsx("span", { className: "ms:px-1.5 ms:py-0.5 ms:rounded ms:bg-mssurface ms:border ms:border-msborder ms:text-mstext ms:font-medium", children: logicTargetQuestionShort })] }), _jsx(LogicEditor, { fieldId: logicField.definition.id, rules: logicField.definition.rules ?? [] })] })) : null })] }));
76
+ }
77
+ function EditTabContent({ fieldId, def, meta, onUpdate, onRenameId, }) {
78
+ const isSection = def.fieldType === 'section';
79
+ if (isSection) {
80
+ return (_jsx(SectionEditContent, { fieldId: fieldId, def: def, onUpdate: onUpdate, onRenameId: onRenameId }));
81
+ }
82
+ return (_jsxs("div", { className: "edit-tab ms:space-y-4", children: [_jsx(CommonEditor, { fieldId: fieldId, def: def, onUpdate: onUpdate, onRenameId: onRenameId }), (meta?.hasOptions || meta?.hasMatrix) && (_jsx("hr", { className: "ms:border-msborder" })), meta?.hasOptions && def.options && (_jsx(OptionListEditor, { fieldId: fieldId, fieldType: def.fieldType, options: def.options })), meta?.hasMatrix && (_jsx(MatrixEditor, { fieldId: fieldId, rows: def.rows ?? [], columns: def.columns ?? [] }))] }));
83
+ }
84
+ function SectionEditContent({ fieldId, def, onUpdate, onRenameId, }) {
85
+ const instanceId = useInstanceId();
86
+ const { selectedFieldId, selectedFieldChildId, editTab, selectFieldChild, setEditTab, } = useUiApi();
87
+ const { normalized } = useFormApi();
88
+ const { field_: activeChildField_ } = useFormApi(selectedFieldId === fieldId && selectedFieldChildId
89
+ ? selectedFieldChildId
90
+ : undefined);
91
+ const childIds = normalized.byId[fieldId]?.childIds ?? [];
92
+ const childFields = childIds
93
+ .map((id) => normalized.byId[id])
94
+ .filter((node) => Boolean(node));
95
+ const activeChildId = selectedFieldId === fieldId ? selectedFieldChildId : null;
96
+ const hasActiveChild = activeChildId !== null &&
97
+ childFields.some((node) => node.definition.id === activeChildId);
98
+ const resolvedActiveChildId = hasActiveChild ? activeChildId : null;
99
+ const activeChildNode = childFields.find((node) => node.definition.id === resolvedActiveChildId);
100
+ const activeChildDef = activeChildNode?.definition;
101
+ const activeChildMeta = activeChildDef
102
+ ? getFieldTypeMeta(activeChildDef.fieldType)
103
+ : undefined;
104
+ React.useEffect(() => {
105
+ if (activeChildId !== null && resolvedActiveChildId !== activeChildId) {
106
+ selectFieldChild(fieldId, resolvedActiveChildId);
107
+ if (editTab === 'logic') {
108
+ setEditTab('logic');
109
+ }
110
+ }
111
+ }, [
112
+ activeChildId,
113
+ editTab,
114
+ fieldId,
115
+ resolvedActiveChildId,
116
+ selectFieldChild,
117
+ setEditTab,
118
+ ]);
119
+ const handleSelectChild = (childId) => {
120
+ selectFieldChild(fieldId, childId);
121
+ if (editTab === 'logic') {
122
+ setEditTab('logic');
123
+ }
124
+ };
125
+ const handleRenameChildId = (newId) => {
126
+ if (!activeChildDef)
127
+ return false;
128
+ const success = activeChildField_.update({ id: newId });
129
+ if (success) {
130
+ selectFieldChild(fieldId, newId);
131
+ }
132
+ return success;
133
+ };
134
+ const handleUpdateChild = (patch) => {
135
+ if (!activeChildDef)
136
+ return;
137
+ activeChildField_.update(patch);
138
+ };
139
+ const handleDeleteChild = () => {
140
+ if (!activeChildDef)
141
+ return;
142
+ activeChildField_.remove();
143
+ const nextChildId = childFields.find((node) => node.definition.id !== activeChildDef.id)?.definition.id;
144
+ selectFieldChild(fieldId, nextChildId ?? null);
145
+ };
146
+ return (_jsxs("div", { className: "section-editor ms:space-y-3", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: `${instanceId}-editor-id-${fieldId}`, className: "edit-label ms:block ms:text-sm ms:font-medium ms:text-mstext ms:mb-1", children: "Section ID" }), _jsx(DraftIdEditor, { id: def.id, fieldId: fieldId, onCommit: onRenameId })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: `${instanceId}-editor-title-${fieldId}`, className: "edit-label ms:block ms:text-sm ms:text-mstext ms:mb-1", children: "Section Title" }), _jsx("input", { id: `${instanceId}-editor-title-${fieldId}`, type: "text", value: def.title ?? '', onChange: (e) => onUpdate({ title: e.currentTarget.value }), placeholder: "Enter section title...", className: "ms:w-full ms:min-w-0 ms:px-3 ms:py-2 ms:text-sm ms:bg-mssurface ms:border ms:border-msborder ms:rounded ms:text-mstext ms:placeholder:text-mstextmuted ms:focus:outline-none ms:focus:ring-1 ms:focus:ring-msprimary ms:focus:border-msprimary ms:transition-colors" })] }), _jsxs("div", { className: "ms:space-y-2", children: [_jsxs("div", { className: "ms:flex ms:items-center ms:justify-between ms:gap-2", children: [_jsx("span", { className: "ms:text-sm ms:font-medium ms:text-mstext", children: "Section Fields" }), _jsxs("span", { className: "ms:text-xs ms:text-mstextmuted", children: [childFields.length, " item", childFields.length === 1 ? '' : 's'] })] }), childFields.length === 0 ? (_jsx("div", { className: "ms:text-sm ms:text-mstextmuted ms:px-3 ms:py-2 ms:bg-msbackground ms:border ms:border-msborder ms:rounded", children: "No fields in this section yet." })) : (_jsxs("select", { id: `${instanceId}-editor-section-child-${fieldId}`, "aria-label": "Section child field selector", className: "ms:w-full ms:min-w-0 ms:px-3 ms:py-2 ms:text-sm ms:bg-mssurface ms:border ms:border-msborder ms:rounded ms:text-mstext ms:focus:outline-none ms:focus:ring-1 ms:focus:ring-msprimary ms:focus:border-msprimary ms:cursor-pointer", value: resolvedActiveChildId ?? '', onChange: (e) => handleSelectChild(e.currentTarget.value), children: [_jsx("option", { value: "", children: "Select a child field\u2026" }), childFields.map((node) => {
147
+ const childMeta = getFieldTypeMeta(node.definition.fieldType);
148
+ const label = node.definition.fieldType === 'section'
149
+ ? node.definition.title || node.definition.id
150
+ : node.definition.question || node.definition.id;
151
+ return (_jsxs("option", { value: node.definition.id, children: [label, " - ", childMeta?.label || node.definition.fieldType] }, node.definition.id));
152
+ })] }))] }), activeChildDef && (_jsxs("div", { className: "ms:space-y-4 ms:p-4 ms:bg-msbackground ms:border ms:border-msborder ms:rounded-lg", children: [_jsxs("div", { className: "ms:flex ms:items-center ms:justify-between ms:gap-2", children: [_jsx("span", { className: "ms:inline-flex ms:items-center ms:px-2.5 ms:py-0.5 ms:rounded-full ms:text-xs ms:font-medium ms:bg-msprimary/10 ms:text-msprimary", children: activeChildMeta?.label || activeChildDef.fieldType }), _jsxs("button", { type: "button", onClick: handleDeleteChild, className: "ms:flex ms:items-center ms:gap-1.5 ms:px-3 ms:py-1.5 ms:text-xs ms:font-medium ms:bg-mssurface ms:text-msdanger ms:hover:text-msdanger ms:hover:bg-msdanger/10 ms:border ms:border-msdanger/50 ms:rounded ms:transition-colors ms:border-0 ms:outline-none ms:focus:outline-none ms:cursor-pointer", title: "Delete this field", children: [_jsx("span", { className: "ms:font-bold", children: "\u00D7" }), "Delete"] })] }), activeChildDef.fieldType === 'section' ? (_jsxs("div", { className: "ms:space-y-3", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: `${instanceId}-editor-active-section-id-${activeChildDef.id}`, className: "edit-label ms:block ms:text-sm ms:font-medium ms:text-mstext ms:mb-1", children: "Section ID" }), _jsx(DraftIdEditor, { id: activeChildDef.id, fieldId: activeChildDef.id, onCommit: handleRenameChildId })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: `${instanceId}-editor-active-section-title-${activeChildDef.id}`, className: "edit-label ms:block ms:text-sm ms:text-mstext ms:mb-1", children: "Section Title" }), _jsx("input", { id: `${instanceId}-editor-active-section-title-${activeChildDef.id}`, type: "text", value: activeChildDef.title ?? '', onChange: (e) => handleUpdateChild({
153
+ title: e.currentTarget.value,
154
+ }), placeholder: "Enter section title...", className: "ms:w-full ms:min-w-0 ms:px-3 ms:py-2 ms:text-sm ms:bg-mssurface ms:border ms:border-msborder ms:rounded ms:text-mstext ms:placeholder:text-mstextmuted ms:focus:outline-none ms:focus:ring-1 ms:focus:ring-msprimary ms:focus:border-msprimary ms:transition-colors" })] })] })) : (_jsx(CommonEditor, { fieldId: activeChildDef.id, def: activeChildDef, onUpdate: handleUpdateChild, onRenameId: handleRenameChildId })), (activeChildMeta?.hasOptions || activeChildMeta?.hasMatrix) && (_jsx("hr", { className: "ms:border-msborder" })), activeChildMeta?.hasOptions &&
155
+ activeChildDef.options && (_jsx(OptionListEditor, { fieldId: activeChildDef.id, fieldType: activeChildDef.fieldType, options: activeChildDef.options })), activeChildMeta?.hasMatrix && (_jsx(MatrixEditor, { fieldId: activeChildDef.id, rows: activeChildDef.rows ?? [], columns: activeChildDef.columns ?? [] }))] }))] }));
156
+ }
@@ -0,0 +1,16 @@
1
+ import type { TextInputType } from '@esheet/core';
2
+ export interface InputTypeEditorProps {
3
+ fieldId: string;
4
+ inputType: TextInputType;
5
+ unit?: string;
6
+ onChange: (patch: {
7
+ inputType?: TextInputType;
8
+ unit?: string;
9
+ }) => void;
10
+ }
11
+ /**
12
+ * InputTypeEditor — dropdown for input type + optional unit selector.
13
+ * Only relevant for `text` and `longtext` fields.
14
+ */
15
+ export declare function InputTypeEditor({ fieldId, inputType, unit, onChange, }: InputTypeEditorProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=InputTypeEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InputTypeEditor.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/edit-panel/InputTypeEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA+BlD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,aAAa,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,aAAa,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACzE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,SAAS,EACT,IAAI,EACJ,QAAQ,GACT,EAAE,oBAAoB,2CA8DtB"}