@_linked/shape-ui 2.0.0 → 2.2.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.
Files changed (210) hide show
  1. package/lib/esm/columns.d.ts +58 -0
  2. package/lib/esm/columns.js +173 -0
  3. package/lib/esm/columns.js.map +1 -0
  4. package/lib/esm/components/AddInstanceForms.d.ts +38 -0
  5. package/lib/esm/components/AddInstanceForms.js +143 -0
  6. package/lib/esm/components/AddInstanceForms.js.map +1 -0
  7. package/lib/esm/components/AddInstanceForms.module.css +12 -0
  8. package/lib/esm/components/BatchEditDrawer.d.ts +13 -0
  9. package/lib/esm/components/BatchEditDrawer.js +122 -0
  10. package/lib/esm/components/BatchEditDrawer.js.map +1 -0
  11. package/lib/esm/components/BatchEditDrawer.module.css +193 -0
  12. package/lib/esm/components/CustomMultiSelect.d.ts +26 -0
  13. package/lib/esm/components/CustomMultiSelect.js +344 -0
  14. package/lib/esm/components/CustomMultiSelect.js.map +1 -0
  15. package/lib/esm/components/CustomMultiSelect.module.css +291 -0
  16. package/lib/esm/components/DynamicFormField.d.ts +21 -0
  17. package/lib/esm/components/DynamicFormField.js +149 -0
  18. package/lib/esm/components/DynamicFormField.js.map +1 -0
  19. package/lib/esm/components/DynamicFormField.module.css +169 -0
  20. package/lib/esm/components/EditInstanceForms.d.ts +15 -0
  21. package/lib/esm/components/EditInstanceForms.js +118 -0
  22. package/lib/esm/components/EditInstanceForms.js.map +1 -0
  23. package/lib/esm/components/FilterBadge.d.ts +20 -0
  24. package/lib/esm/components/FilterBadge.js +26 -0
  25. package/lib/esm/components/FilterBadge.js.map +1 -0
  26. package/lib/esm/components/FilterBadge.module.css +25 -0
  27. package/lib/esm/components/FormField.d.ts +18 -0
  28. package/lib/esm/components/FormField.js +9 -0
  29. package/lib/esm/components/FormField.js.map +1 -0
  30. package/lib/esm/components/FormField.module.css +57 -0
  31. package/lib/esm/components/InstanceDeletionDialog.d.ts +17 -0
  32. package/lib/esm/components/InstanceDeletionDialog.js +83 -0
  33. package/lib/esm/components/InstanceDeletionDialog.js.map +1 -0
  34. package/lib/esm/components/InstanceDeletionDialog.module.css +59 -0
  35. package/lib/esm/components/InstanceOverview.d.ts +37 -0
  36. package/lib/esm/components/InstanceOverview.js +233 -0
  37. package/lib/esm/components/InstanceOverview.js.map +1 -0
  38. package/lib/esm/components/InstanceOverview.module.css +35 -0
  39. package/lib/esm/components/InstanceView.d.ts +29 -0
  40. package/lib/esm/components/InstanceView.js +97 -0
  41. package/lib/esm/components/InstanceView.js.map +1 -0
  42. package/lib/esm/components/InstanceView.module.css +157 -0
  43. package/lib/esm/components/NodeBadge.d.ts +25 -0
  44. package/lib/esm/components/NodeBadge.js +29 -0
  45. package/lib/esm/components/NodeBadge.js.map +1 -0
  46. package/lib/esm/components/NodeValuesEditor.d.ts +15 -0
  47. package/lib/esm/components/NodeValuesEditor.js +29 -0
  48. package/lib/esm/components/NodeValuesEditor.js.map +1 -0
  49. package/lib/esm/components/NodeValuesEditor.module.css +4 -0
  50. package/lib/esm/components/ReactTable.d.ts +50 -0
  51. package/lib/esm/components/ReactTable.js +370 -0
  52. package/lib/esm/components/ReactTable.js.map +1 -0
  53. package/lib/esm/components/ReactTable.module.css +950 -0
  54. package/lib/esm/components/TextfieldEditor.d.ts +1 -2
  55. package/lib/esm/components/TextfieldEditor.js +2 -2
  56. package/lib/esm/components/TextfieldEditor.js.map +1 -1
  57. package/lib/esm/components/forms.module.css +96 -0
  58. package/lib/esm/host.d.ts +196 -0
  59. package/lib/esm/host.js +25 -0
  60. package/lib/esm/host.js.map +1 -0
  61. package/lib/esm/hostContext.d.ts +34 -0
  62. package/lib/esm/hostContext.js +95 -0
  63. package/lib/esm/hostContext.js.map +1 -0
  64. package/lib/esm/index.d.ts +64 -10
  65. package/lib/esm/index.js +63 -14
  66. package/lib/esm/index.js.map +1 -1
  67. package/lib/esm/read.d.ts +48 -0
  68. package/lib/esm/read.js +96 -0
  69. package/lib/esm/read.js.map +1 -0
  70. package/lib/esm/search.d.ts +32 -0
  71. package/lib/esm/search.js +87 -0
  72. package/lib/esm/search.js.map +1 -0
  73. package/lib/esm/shape/contracts.d.ts +58 -0
  74. package/lib/esm/shape/contracts.js +15 -0
  75. package/lib/esm/shape/contracts.js.map +1 -0
  76. package/lib/esm/shape/naming.d.ts +30 -0
  77. package/lib/esm/shape/naming.js +52 -0
  78. package/lib/esm/shape/naming.js.map +1 -0
  79. package/lib/esm/shape/nodeDisplay.d.ts +36 -0
  80. package/lib/esm/shape/nodeDisplay.js +117 -0
  81. package/lib/esm/shape/nodeDisplay.js.map +1 -0
  82. package/lib/esm/shape/propertyTypes.d.ts +12 -0
  83. package/lib/esm/shape/propertyTypes.js +60 -0
  84. package/lib/esm/shape/propertyTypes.js.map +1 -0
  85. package/lib/esm/shape/propertyVisibility.d.ts +56 -0
  86. package/lib/esm/shape/propertyVisibility.js +112 -0
  87. package/lib/esm/shape/propertyVisibility.js.map +1 -0
  88. package/lib/esm/shape/shapeDependencies.d.ts +41 -0
  89. package/lib/esm/shape/shapeDependencies.js +141 -0
  90. package/lib/esm/shape/shapeDependencies.js.map +1 -0
  91. package/lib/esm/shape/tableFilters.d.ts +35 -0
  92. package/lib/esm/shape/tableFilters.js +171 -0
  93. package/lib/esm/shape/tableFilters.js.map +1 -0
  94. package/lib/esm/shape/useFormDependencies.d.ts +38 -0
  95. package/lib/esm/shape/useFormDependencies.js +178 -0
  96. package/lib/esm/shape/useFormDependencies.js.map +1 -0
  97. package/lib/esm/shape/validation.d.ts +15 -0
  98. package/lib/esm/shape/validation.js +106 -0
  99. package/lib/esm/shape/validation.js.map +1 -0
  100. package/lib/esm/tokens.css +25 -0
  101. package/lib/esm/types.d.ts +22 -0
  102. package/lib/esm/types.js +1 -0
  103. package/lib/esm/types.js.map +1 -0
  104. package/lib/esm/write.d.ts +51 -0
  105. package/lib/esm/write.js +99 -0
  106. package/lib/esm/write.js.map +1 -0
  107. package/package.json +44 -18
  108. package/src/backend.ts +1 -0
  109. package/src/columns.regression.test.ts +143 -0
  110. package/src/columns.test.ts +200 -0
  111. package/src/columns.ts +228 -0
  112. package/src/components/AddInstanceForms.module.css +12 -0
  113. package/src/components/AddInstanceForms.tsx +321 -0
  114. package/src/components/AvatarEditor.tsx +34 -0
  115. package/src/components/BatchEditDrawer.module.css +193 -0
  116. package/src/components/BatchEditDrawer.tsx +295 -0
  117. package/src/components/CheckboxEditor.module.css +6 -0
  118. package/src/components/CheckboxEditor.tsx +86 -0
  119. package/src/components/CustomMultiSelect.module.css +291 -0
  120. package/src/components/CustomMultiSelect.tsx +527 -0
  121. package/src/components/DateEditor.tsx +50 -0
  122. package/src/components/DynamicFormField.module.css +169 -0
  123. package/src/components/DynamicFormField.tsx +357 -0
  124. package/src/components/EditInstanceForms.tsx +260 -0
  125. package/src/components/FilterBadge.module.css +25 -0
  126. package/src/components/FilterBadge.tsx +70 -0
  127. package/src/components/FormField.module.css +57 -0
  128. package/src/components/FormField.tsx +59 -0
  129. package/src/components/ImageValueEditor.module.css +3 -0
  130. package/src/components/ImageValueEditor.tsx +82 -0
  131. package/src/components/InstanceDeletionDialog.module.css +59 -0
  132. package/src/components/InstanceDeletionDialog.tsx +175 -0
  133. package/src/components/InstanceOverview.module.css +35 -0
  134. package/src/components/InstanceOverview.tsx +374 -0
  135. package/src/components/InstanceView.module.css +157 -0
  136. package/src/components/InstanceView.tsx +212 -0
  137. package/src/components/NodeBadge.tsx +60 -0
  138. package/src/components/NodeValuesEditor.module.css +4 -0
  139. package/src/components/NodeValuesEditor.tsx +64 -0
  140. package/src/components/RadioButtonEditor.module.css +22 -0
  141. package/src/components/RadioButtonEditor.tsx +96 -0
  142. package/src/components/ReactTable.module.css +950 -0
  143. package/src/components/ReactTable.tsx +991 -0
  144. package/src/components/SelectEditor.tsx +85 -0
  145. package/src/components/ShapeTable.module.css +394 -0
  146. package/src/components/SwitchEditor.tsx +60 -0
  147. package/src/components/TextareaEditor.tsx +55 -0
  148. package/src/components/TextfieldEditor.tsx +67 -0
  149. package/src/components/ToggleEditor.tsx +63 -0
  150. package/src/components/forms.module.css +96 -0
  151. package/src/host.ts +226 -0
  152. package/src/hostContext.tsx +114 -0
  153. package/src/index.ts +99 -0
  154. package/src/package.ts +21 -0
  155. package/src/read.ts +148 -0
  156. package/src/search.test.ts +174 -0
  157. package/src/search.ts +108 -0
  158. package/src/shape/contracts.ts +66 -0
  159. package/src/shape/naming.ts +52 -0
  160. package/src/shape/nodeDisplay.ts +114 -0
  161. package/src/shape/propertyTypes.ts +86 -0
  162. package/src/shape/propertyVisibility.ts +180 -0
  163. package/src/shape/shapeDependencies.ts +183 -0
  164. package/src/shape/tableFilters.ts +223 -0
  165. package/src/shape/types.ts +1158 -0
  166. package/src/shape/useFormDependencies.ts +257 -0
  167. package/src/shape/validation.ts +121 -0
  168. package/src/tokens.css +25 -0
  169. package/src/types.ts +20 -0
  170. package/src/utils/helper.ts +35 -0
  171. package/src/write.test.ts +174 -0
  172. package/src/write.ts +114 -0
  173. package/.changeset/README.md +0 -20
  174. package/.changeset/config.json +0 -14
  175. package/.github/workflows/changeset-check.yml +0 -44
  176. package/.github/workflows/ci.yml +0 -43
  177. package/.github/workflows/publish.yml +0 -158
  178. package/CHANGELOG.md +0 -30
  179. package/lib/esm/components/AvatarEditor.d.ts +0 -20
  180. package/lib/esm/components/AvatarEditor.js +0 -7
  181. package/lib/esm/components/AvatarEditor.js.map +0 -1
  182. package/lib/esm/components/CheckboxEditor.d.ts +0 -21
  183. package/lib/esm/components/CheckboxEditor.js +0 -44
  184. package/lib/esm/components/CheckboxEditor.js.map +0 -1
  185. package/lib/esm/components/DateEditor.d.ts +0 -20
  186. package/lib/esm/components/DateEditor.js +0 -22
  187. package/lib/esm/components/DateEditor.js.map +0 -1
  188. package/lib/esm/components/RadioButtonEditor.d.ts +0 -23
  189. package/lib/esm/components/RadioButtonEditor.js +0 -41
  190. package/lib/esm/components/RadioButtonEditor.js.map +0 -1
  191. package/lib/esm/components/SelectEditor.d.ts +0 -21
  192. package/lib/esm/components/SelectEditor.js +0 -30
  193. package/lib/esm/components/SelectEditor.js.map +0 -1
  194. package/lib/esm/components/SwitchEditor.d.ts +0 -21
  195. package/lib/esm/components/SwitchEditor.js +0 -23
  196. package/lib/esm/components/SwitchEditor.js.map +0 -1
  197. package/lib/esm/components/TextareaEditor.d.ts +0 -23
  198. package/lib/esm/components/TextareaEditor.js +0 -15
  199. package/lib/esm/components/TextareaEditor.js.map +0 -1
  200. package/lib/esm/components/ToggleEditor.d.ts +0 -22
  201. package/lib/esm/components/ToggleEditor.js +0 -23
  202. package/lib/esm/components/ToggleEditor.js.map +0 -1
  203. package/lib/esm/ontologies/lincd-ui.d.ts +0 -6
  204. package/lib/esm/ontologies/lincd-ui.js +0 -18
  205. package/lib/esm/ontologies/lincd-ui.js.map +0 -1
  206. package/lib/esm/package.d.ts +0 -3
  207. package/lib/esm/package.js +0 -4
  208. package/lib/esm/package.js.map +0 -1
  209. package/tsconfig-cjs.json +0 -8
  210. package/tsconfig-esm.json +0 -9
@@ -0,0 +1,169 @@
1
+ /* ============================================================================
2
+ DynamicFormField.module.css
3
+
4
+ Dynamic form field component for various input types.
5
+ Uses semantic tokens from theme.css.
6
+ ============================================================================ */
7
+
8
+ /* === Form Row === */
9
+ .form {
10
+ display: flex;
11
+ min-height: 3.75rem;
12
+ align-items: center;
13
+ border-top: 1px solid var(--table-row-border);
14
+ justify-content: flex-start;
15
+ padding: var(--space-sm) 0;
16
+ }
17
+
18
+ .form:first-child {
19
+ border-top: none;
20
+ }
21
+
22
+ /* === Label Column === */
23
+ .label {
24
+ display: flex;
25
+ flex-direction: column;
26
+ width: 30%;
27
+ height: 100%;
28
+ justify-content: center;
29
+ }
30
+
31
+ .label label {
32
+ font-weight: 400;
33
+ color: var(--text-auxiliary);
34
+ font-size: var(--typography-font-size);
35
+ }
36
+
37
+ .label p {
38
+ color: var(--table-cell-text);
39
+ color: var(--text-auxiliary);
40
+ font-size: var(--typography-small-size);
41
+ margin-top: 0.25rem;
42
+ }
43
+
44
+ .label sup {
45
+ color: var(--intent-danger-icon);
46
+ margin-left: var(--space-2xs);
47
+ }
48
+
49
+ /* === Value Column === */
50
+ .value {
51
+ display: flex;
52
+ gap: var(--space-sm-plus);
53
+ width: 70%;
54
+ height: 100%;
55
+ align-items: center;
56
+ flex-wrap: wrap;
57
+ }
58
+
59
+ /* === Text Form Field === */
60
+ .textFormField {
61
+ box-shadow: none;
62
+ border: 1px solid var(--field-border);
63
+ border-radius: var(--radius-field);
64
+ margin: var(--space-sm) 0;
65
+ padding: var(--space-sm-plus) var(--field-padding-x);
66
+ background-color: var(--field-bg);
67
+ color: var(--field-text);
68
+ font-size: var(--typography-small-size);
69
+ width: 100%;
70
+ transition: all var(--transition-fast);
71
+ }
72
+
73
+ .textFormField:focus {
74
+ outline: none;
75
+ border-color: var(--field-border-focus);
76
+ box-shadow: 0 0 0 3px
77
+ color-mix(in srgb, var(--dynamic-form-field-tertiary-500, var(--color-tertiary-500)) 15%, transparent);
78
+ }
79
+
80
+ .textFormField::placeholder {
81
+ color: var(--field-text-placeholder);
82
+ }
83
+
84
+ /* === Select Field (sh:in dropdown) === */
85
+ .selectField {
86
+ box-shadow: none;
87
+ border: 1px solid var(--field-border);
88
+ border-radius: var(--radius-field);
89
+ margin: var(--space-sm) 0;
90
+ padding: var(--space-sm-plus) var(--field-padding-x);
91
+ background-color: var(--field-bg);
92
+ color: var(--field-text);
93
+ font-size: var(--typography-small-size);
94
+ width: 100%;
95
+ transition: all var(--transition-fast);
96
+ cursor: pointer;
97
+ }
98
+
99
+ .selectField:focus {
100
+ outline: none;
101
+ border-color: var(--field-border-focus);
102
+ box-shadow: 0 0 0 3px
103
+ color-mix(in srgb, var(--dynamic-form-field-tertiary-500, var(--color-tertiary-500)) 15%, transparent);
104
+ }
105
+
106
+ /* === Field Error === */
107
+ .fieldError {
108
+ display: block;
109
+ width: 100%;
110
+ color: var(--intent-danger-text);
111
+ font-size: var(--typography-small-size);
112
+ margin-top: -0.25rem;
113
+ padding-bottom: var(--space-2xs);
114
+ }
115
+
116
+ /* === Date Form Field === */
117
+ .dateFormField {
118
+ background-color: var(--field-bg);
119
+ border: 1px solid var(--field-border);
120
+ border-radius: var(--radius-field);
121
+ padding: var(--space-sm-plus) var(--field-padding-x);
122
+ color: var(--field-text);
123
+ font-size: var(--typography-small-size);
124
+ width: 100%;
125
+ }
126
+
127
+ .dateFormField:focus {
128
+ outline: none;
129
+ border-color: var(--field-border-focus);
130
+ }
131
+
132
+ /* === Info Icon (tooltip trigger) === */
133
+ .infoIcon {
134
+ display: inline-flex;
135
+ align-items: center;
136
+ margin-left: var(--space-2xs);
137
+ color: var(--text-auxiliary);
138
+ cursor: help;
139
+ vertical-align: middle;
140
+ background: none;
141
+ border: none;
142
+ padding: 0;
143
+ line-height: 1;
144
+ }
145
+
146
+ .infoIcon:hover,
147
+ .infoIcon:focus-visible {
148
+ color: var(--dynamic-form-field-tertiary-500, var(--color-tertiary-500));
149
+ outline: none;
150
+ }
151
+
152
+ /* === Narrowing Hint (shown when parent field can narrow this field's options) === */
153
+ .narrowingHint {
154
+ display: block;
155
+ width: 100%;
156
+ color: var(--text-auxiliary);
157
+ font-size: var(--typography-small-size);
158
+ font-style: italic;
159
+ margin-bottom: var(--space-2xs);
160
+ }
161
+
162
+ /* === Tooltip Content (ensure it appears above form fields) === */
163
+ .tooltipContent {
164
+ z-index: var(--layer-tooltip);
165
+ }
166
+
167
+ /* All tokens used above (--text-secondary, --text-auxiliary, --input-*,
168
+ --intent-danger-*) are semantic and auto-switch in dark mode. */
169
+
@@ -0,0 +1,357 @@
1
+ import { Tooltip } from '@_linked/primitives/components/Tooltip';
2
+ import type { PropertyShapeWire } from '@_linked/core/shapes/nodeShapeWire';
3
+ import { shacl } from '@_linked/core/ontologies/shacl';
4
+ import { xsd } from '@_linked/core/ontologies/xsd';
5
+ import { Shape } from '@_linked/core/shapes/Shape';
6
+ import { useState } from 'react';
7
+ import { Icons } from '@_linked/icons';
8
+ import type { FieldDependencyState } from '../shape/useFormDependencies.js';
9
+ import { getNodeDisplay, humanizeEnumLabel, enumOptions } from '../shape/nodeDisplay.js';
10
+ import { validateField } from '../shape/validation.js';
11
+ import style from './DynamicFormField.module.css';
12
+ import { FormField } from './FormField.js';
13
+ import NodeValuesEditor from './NodeValuesEditor.js';
14
+
15
+ interface DynamicFormProps {
16
+ dataType?;
17
+ formName: string;
18
+ label: string;
19
+ placeholder: string;
20
+ required: boolean;
21
+ value?;
22
+ shape;
23
+ of: Shape;
24
+ property: PropertyShapeWire;
25
+ onBeforeNavigate?: () => Promise<string | void> | string | void;
26
+ /** External error from submit-time validation */
27
+ error?: string;
28
+ /** Dependency state from parent fields (visibility + narrowing) */
29
+ dependencyState?: FieldDependencyState;
30
+ }
31
+
32
+ // Helper: format a Date or ISO string for <input type="date">
33
+ function toDateInputValue(val: any): string {
34
+ if (!val) return '';
35
+ if (val instanceof Date) return val.toISOString().split('T')[0];
36
+ if (typeof val === 'string') return val.split('T')[0];
37
+ return '';
38
+ }
39
+
40
+ // Helper: format a Date or ISO string for <input type="datetime-local">
41
+ function toDateTimeInputValue(val: any): string {
42
+ if (!val) return '';
43
+ if (val instanceof Date) return val.toISOString().slice(0, 16);
44
+ if (typeof val === 'string') return val.slice(0, 16);
45
+ return '';
46
+ }
47
+
48
+ /** Compute HTML min/max attrs from SHACL inclusive/exclusive constraints */
49
+ function computeNumericBounds(property: PropertyShapeWire, isInteger: boolean) {
50
+ const attrs: Record<string, number> = {};
51
+ if (property.minInclusive != null) {
52
+ attrs.min = property.minInclusive;
53
+ } else if (property.minExclusive != null) {
54
+ // The metamodel types the bound as `string | number` — a SHACL literal can be either —
55
+ // where the DTO had already coerced it to a number.
56
+ const bound = Number(property.minExclusive);
57
+ attrs.min = isInteger ? bound + 1 : bound;
58
+ }
59
+ if (property.maxInclusive != null) {
60
+ attrs.max = property.maxInclusive;
61
+ } else if (property.maxExclusive != null) {
62
+ attrs.max = isInteger ? property.maxExclusive - 1 : property.maxExclusive;
63
+ }
64
+ return attrs;
65
+ }
66
+
67
+ function FieldWrapper({
68
+ label,
69
+ required,
70
+ error,
71
+ description,
72
+ children,
73
+ }: {
74
+ label: string;
75
+ required: boolean;
76
+ error?: string;
77
+ description?: string;
78
+ children: React.ReactNode;
79
+ }) {
80
+ return (
81
+ <div className={style.form}>
82
+ <div className={style.label}>
83
+ <label>
84
+ {label} {required && <sup>*</sup>}
85
+ {description && (
86
+ <Tooltip.Root>
87
+ <Tooltip.Trigger asChild>
88
+ <button
89
+ type="button"
90
+ className={style.infoIcon}
91
+ aria-label={`Info about ${label}`}
92
+ >
93
+ <Icons.Info width={14} height={14} />
94
+ </button>
95
+ </Tooltip.Trigger>
96
+ <Tooltip.Content side="top" sideOffset={4} className={style.tooltipContent}>
97
+ {description}
98
+ <Tooltip.Arrow />
99
+ </Tooltip.Content>
100
+ </Tooltip.Root>
101
+ )}
102
+ </label>
103
+ </div>
104
+ <div className={style.value}>
105
+ {children}
106
+ {error && <span className={style.fieldError}>{error}</span>}
107
+ </div>
108
+ </div>
109
+ );
110
+ }
111
+
112
+ function DynamicForm({
113
+ dataType,
114
+ formName,
115
+ label,
116
+ placeholder = 'Type here',
117
+ required = false,
118
+ value,
119
+ shape,
120
+ of,
121
+ property,
122
+ onBeforeNavigate,
123
+ error: externalError,
124
+ dependencyState,
125
+ }: DynamicFormProps) {
126
+ const [localError, setLocalError] = useState('');
127
+ const displayError = externalError || localError;
128
+
129
+ const datatype = property.datatype;
130
+ const nodeKind = property.nodeKind;
131
+ const valueShape = property.valueShape;
132
+
133
+ const handleBlur = (val: any) => {
134
+ const err = validateField(property, val);
135
+ setLocalError(err);
136
+ };
137
+
138
+ // Guard: skip properties without nodeKind (incomplete shape data)
139
+ if (!nodeKind) return null;
140
+
141
+ // ── sh:in enumeration → render as <select> dropdown (both literal and IRI) ──
142
+ //
143
+ // `enumOptions` derives the label. `sh:in` carries members, not names — the DTO's `inValues`
144
+ // looked like it carried names but they were the IRI's local part, computed at projection
145
+ // time. Same answer, computed in one place.
146
+ const choices = enumOptions(property);
147
+ if (choices.length > 0) {
148
+ const currentValue =
149
+ value !== undefined && value !== null
150
+ ? typeof value === 'object' && value.id
151
+ ? value.id
152
+ : String(value)
153
+ : '';
154
+ return (
155
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
156
+ <select
157
+ className={style.selectField}
158
+ required={required}
159
+ defaultValue={currentValue}
160
+ onChange={(e) => {
161
+ of[property.label] = e.target.value || undefined;
162
+ }}
163
+ onBlur={(e) => handleBlur(e.target.value)}
164
+ >
165
+ <option value="">— Select —</option>
166
+ {choices.map((opt) => (
167
+ <option key={opt.id} value={opt.id}>
168
+ {opt.label}
169
+ </option>
170
+ ))}
171
+ </select>
172
+ </FieldWrapper>
173
+ );
174
+ }
175
+
176
+ // Object / IRI properties — rendered by NodeValuesEditor (CustomMultiSelect)
177
+ if (
178
+ nodeKind.id === shacl.IRI.id ||
179
+ nodeKind.id === shacl.BlankNode.id ||
180
+ nodeKind.id === shacl.BlankNodeOrIRI.id
181
+ ) {
182
+ // Dependency hint: show subtle text when parent field can narrow this field
183
+ const narrowingHint =
184
+ dependencyState && !dependencyState.parentHasValue
185
+ ? dependencyState.parentLabels
186
+ .map((l) => l.replace(/([A-Z])/g, ' $1').replace(/^./, (c) => c.toUpperCase()).trim())
187
+ .join(' or ')
188
+ : null;
189
+
190
+ return (
191
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
192
+ {narrowingHint && (
193
+ <span className={style.narrowingHint}>
194
+ Select {narrowingHint} to narrow options
195
+ </span>
196
+ )}
197
+ <NodeValuesEditor
198
+ sourceShape={shape}
199
+ of={of}
200
+ property={property}
201
+ onBeforeNavigate={onBeforeNavigate}
202
+ narrowedIds={dependencyState?.narrowedIds}
203
+ isNarrowing={dependencyState?.loading}
204
+ />
205
+ </FieldWrapper>
206
+ );
207
+ }
208
+
209
+ // Literal properties
210
+ if (nodeKind.id === shacl.Literal.id) {
211
+ const dt = datatype?.id;
212
+
213
+ // Boolean → native checkbox
214
+ if (dt === xsd.boolean.id) {
215
+ return (
216
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
217
+ <input
218
+ type="checkbox"
219
+ className={style.checkboxField}
220
+ defaultChecked={!!value}
221
+ onChange={(e) => { of[property.label] = e.target.checked; }}
222
+ />
223
+ </FieldWrapper>
224
+ );
225
+ }
226
+
227
+ // Date
228
+ if (dt === xsd.date.id) {
229
+ return (
230
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
231
+ <input
232
+ type="date"
233
+ className={style.textFormField}
234
+ required={required}
235
+ defaultValue={toDateInputValue(value)}
236
+ onChange={(e) => { of[property.label] = e.target.value; }}
237
+ onBlur={(e) => handleBlur(e.target.value)}
238
+ />
239
+ </FieldWrapper>
240
+ );
241
+ }
242
+
243
+ // DateTime
244
+ if (dt === xsd.dateTime.id) {
245
+ return (
246
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
247
+ <input
248
+ type="datetime-local"
249
+ className={style.textFormField}
250
+ required={required}
251
+ defaultValue={toDateTimeInputValue(value)}
252
+ onChange={(e) => { of[property.label] = e.target.value; }}
253
+ onBlur={(e) => handleBlur(e.target.value)}
254
+ />
255
+ </FieldWrapper>
256
+ );
257
+ }
258
+
259
+ // Integer / long
260
+ if (
261
+ dt === xsd.integer.id ||
262
+ dt === xsd.long?.id
263
+ ) {
264
+ const bounds = computeNumericBounds(property, true);
265
+ return (
266
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
267
+ <input
268
+ type="number"
269
+ step="1"
270
+ className={style.textFormField}
271
+ required={required}
272
+ defaultValue={value !== undefined && value !== null ? Number(value) : ''}
273
+ {...bounds}
274
+ onChange={(e) => { of[property.label] = parseInt(e.target.value, 10); }}
275
+ onBlur={(e) => handleBlur(parseInt(e.target.value, 10))}
276
+ />
277
+ </FieldWrapper>
278
+ );
279
+ }
280
+
281
+ // Decimal / float / double
282
+ if (
283
+ dt === xsd.decimal?.id ||
284
+ dt === xsd.float?.id ||
285
+ dt === xsd.double?.id
286
+ ) {
287
+ const bounds = computeNumericBounds(property, false);
288
+ return (
289
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
290
+ <input
291
+ type="number"
292
+ step="any"
293
+ className={style.textFormField}
294
+ required={required}
295
+ defaultValue={value !== undefined && value !== null ? Number(value) : ''}
296
+ {...bounds}
297
+ onChange={(e) => { of[property.label] = parseFloat(e.target.value); }}
298
+ onBlur={(e) => handleBlur(parseFloat(e.target.value))}
299
+ />
300
+ </FieldWrapper>
301
+ );
302
+ }
303
+
304
+ // URI / anyURI
305
+ if (dt === 'http://www.w3.org/2001/XMLSchema#anyURI') {
306
+ return (
307
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
308
+ <FormField
309
+ name={formName}
310
+ type="url"
311
+ placeholder={placeholder}
312
+ className={style.textFormField}
313
+ required={required}
314
+ defaultValue={typeof value === 'string' ? value : ''}
315
+ onChange={(e) => { of[property.label] = e.target.value; }}
316
+ onBlur={(e) => handleBlur(e.target.value)}
317
+ />
318
+ </FieldWrapper>
319
+ );
320
+ }
321
+
322
+ // String (default) — also catches unknown datatypes
323
+ const strAttrs: Record<string, any> = {};
324
+ if (property.minLength != null) strAttrs.minLength = property.minLength;
325
+ if (property.maxLength != null) strAttrs.maxLength = property.maxLength;
326
+ if (property.pattern) strAttrs.pattern = property.pattern;
327
+
328
+ return (
329
+ <FieldWrapper label={label} required={required} error={displayError} description={property.description}>
330
+ <FormField
331
+ name={formName}
332
+ type="text"
333
+ placeholder={placeholder}
334
+ className={style.textFormField}
335
+ required={required}
336
+ defaultValue={
337
+ value !== undefined && value !== null
338
+ ? typeof value === 'object'
339
+ ? getNodeDisplay(value)
340
+ : String(value)
341
+ : ''
342
+ }
343
+ {...strAttrs}
344
+ onChange={(e) => { of[property.label] = e.target.value; }}
345
+ onBlur={(e) => {
346
+ of[property.label] = e.target.value;
347
+ handleBlur(e.target.value);
348
+ }}
349
+ />
350
+ </FieldWrapper>
351
+ );
352
+ }
353
+
354
+ return null;
355
+ }
356
+
357
+ export { DynamicForm };