@_linked/shape-ui 2.0.0 → 2.3.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 (207) 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 +43 -0
  36. package/lib/esm/components/InstanceOverview.js +236 -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 +59 -0
  51. package/lib/esm/components/ReactTable.js +378 -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 +60 -10
  65. package/lib/esm/index.js +60 -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 +385 -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 +1013 -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 +93 -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/tsconfig-cjs.json +0 -8
  207. package/tsconfig-esm.json +0 -9
@@ -0,0 +1,96 @@
1
+ /* ============================================================================
2
+ EditInstanceForms.module.css
3
+
4
+ Form layout for editing instance data.
5
+ Matches ViewInstance styling with card container.
6
+ Uses semantic tokens from theme.css.
7
+ ============================================================================ */
8
+
9
+ /* === Container (Card) === */
10
+ .container {
11
+ background: var(--surface-bg);
12
+ border: 1px solid var(--surface-border);
13
+ border-radius: var(--radius-box);
14
+ margin: var(--section-gap) 0;
15
+ }
16
+
17
+ /* === Rows Wrapper === */
18
+ .rows {
19
+ padding: var(--surface-gap) var(--space-2xl) var(--space-lg) var(--space-2xl);
20
+ }
21
+
22
+ /* === Section Title === */
23
+ .sectionTitle {
24
+ margin: 0;
25
+ padding: var(--surface-padding) var(--space-2xl) 0 var(--space-2xl);
26
+ font-size: var(--typography-small-size);
27
+ color: var(--forms-gray-500, var(--color-gray-500));
28
+ font-weight: 600;
29
+ text-transform: uppercase;
30
+ letter-spacing: 0.05em;
31
+ }
32
+
33
+ /* === Add Field Section === */
34
+ .addFieldSection {
35
+ padding: var(--space-sm) 0;
36
+ }
37
+
38
+ /* === Save Error === */
39
+ .saveError {
40
+ color: var(--intent-danger-text);
41
+ font-size: var(--typography-small-size);
42
+ margin: var(--space-sm) 0 0;
43
+ }
44
+
45
+ /* === Actions === */
46
+ .actions {
47
+ display: flex;
48
+ justify-content: flex-end;
49
+ gap: var(--surface-gap);
50
+ margin-top: var(--space-lg);
51
+ }
52
+
53
+ /* ============================================================================
54
+ Dark Mode
55
+ ============================================================================ */
56
+
57
+ /* .container uses --card-bg / --card-border which auto-switch in dark mode */
58
+
59
+ [data-theme='dark'] .sectionTitle {
60
+ color: var(--forms-gray-400, var(--color-gray-400));
61
+ }
62
+
63
+ /* ============================================================================
64
+ Mobile Responsive Styles
65
+ ============================================================================ */
66
+
67
+ @media only screen and (max-width: 768px) {
68
+ .rows {
69
+ padding: var(--space-sm) var(--space-md);
70
+ }
71
+ }
72
+
73
+ @media only screen and (max-width: 480px) {
74
+ .container {
75
+ margin: var(--space-md) 0;
76
+ border-radius: var(--radius-field);
77
+ }
78
+
79
+ .rows {
80
+ padding: var(--space-xs) var(--space-sm);
81
+ }
82
+
83
+ .sectionTitle {
84
+ padding: var(--space-sm) var(--space-sm) 0 var(--space-sm);
85
+ font-size: 0.6875rem;
86
+ }
87
+
88
+ .actions {
89
+ flex-direction: column-reverse;
90
+ gap: var(--space-sm);
91
+ }
92
+
93
+ .actions button {
94
+ width: 100%;
95
+ }
96
+ }
package/src/host.ts ADDED
@@ -0,0 +1,226 @@
1
+ /**
2
+ * What a host application supplies to the data manager.
3
+ *
4
+ * The point of this contract is what it does NOT contain: no project id, no branch, no
5
+ * store, no dataset name. Routing is decided below the component by `LinkedStorage` —
6
+ * `AppDataRouter` inside Create Now, the app's own default dataset in a standalone or
7
+ * Capacitor build — so the component issues a Linked Query and never names a project.
8
+ * Everything here is genuinely host-specific: where IRIs are minted, how the catalog is
9
+ * found, and where a click should go.
10
+ */
11
+
12
+ import type {NodeShapeWire} from '@_linked/core/shapes/nodeShapeWire';
13
+ import type {ReactNode} from 'react';
14
+
15
+ /** Where a host sends the viewer when they act on a row. */
16
+ export interface DataManagerNavigation {
17
+ toInstance(shapeIri: string, instanceId: string): void;
18
+ toEdit(shapeIri: string, instanceId: string): void;
19
+ toAdd(shapeIri: string): void;
20
+ }
21
+
22
+ /**
23
+ * "Pick mode" — the flow where a form asks the user to choose a related instance and
24
+ * returns with it. Optional because it is a Create Now studio affordance; a standalone
25
+ * app that has no such flow simply omits it, rather than being asked to stub it.
26
+ */
27
+ /** What a field needs the host to open a picker for. */
28
+ export interface PickRequest {
29
+ /** The shape whose instances the viewer should choose from. */
30
+ shapeIri: string;
31
+ /** The field asking, so the host can route the answer back to it. */
32
+ propertyLabel: string;
33
+ /** The shape being edited — the same field label can exist on more than one. */
34
+ sourceShapeIri?: string;
35
+ minCount?: number;
36
+ maxCount?: number;
37
+ /** Already-chosen values, so the picker can show them as selected. */
38
+ alreadySelected?: InstanceSuggestion[];
39
+ /**
40
+ * An opaque token the host must carry across the round trip and hand back on return.
41
+ *
42
+ * A field with unsaved work flushes it before the viewer leaves, and that flush yields
43
+ * an identifier — Create Now's draft id — without which the half-filled form is lost on
44
+ * the way back. The field cannot store it itself; it is about to unmount. It is opaque
45
+ * here on purpose: how a host preserves it is its own business (Create Now puts it in
46
+ * the return URL), and a host with no navigation to survive can ignore it.
47
+ */
48
+ resumeToken?: string;
49
+ }
50
+
51
+ export interface DataManagerPicking {
52
+ /**
53
+ * A field is asking the viewer to choose. How that is presented is entirely the host's
54
+ * call — Create Now navigates to the overview in pick mode, a mobile app would open a
55
+ * sheet — which is why this is a request rather than a URL.
56
+ *
57
+ * A host that omits `picking` gets a picker with no "browse all" affordance rather than
58
+ * a broken one; the inline search still works.
59
+ */
60
+ open(request: PickRequest): void;
61
+
62
+ /**
63
+ * The viewer completed a choice.
64
+ *
65
+ * Called ONCE per completed choice with everything chosen, not once per instance —
66
+ * a multi-pick host has to write all the values and then return, and per-instance calls
67
+ * made it navigate away on the first one.
68
+ *
69
+ * The full suggestion is passed, not just the id, because the host generally has to
70
+ * store something displayable straight away: the field that opened the picker renders
71
+ * a chip before anything re-reads the instance. Passing an id alone forced the host to
72
+ * look the label back up, which is a read it should not need.
73
+ */
74
+ select(instances: InstanceSuggestion[]): void;
75
+
76
+ /**
77
+ * The return leg: values the viewer picked for this field while it was away.
78
+ *
79
+ * A field that sends the viewer elsewhere to choose is unmounted by the time they do,
80
+ * so the answer cannot be handed back through a callback — the field has to ask for it
81
+ * when it mounts again. Which is why `open` and `select` alone were not enough: the
82
+ * component was reading Create Now's sessionStorage directly to close the loop.
83
+ *
84
+ * Returns only what belongs to THIS field: several relation fields can be on one form,
85
+ * and the picked values must not land in the wrong one. Returns null when there is
86
+ * nothing waiting, which is the normal case on almost every mount.
87
+ *
88
+ * The host consumes the result. Note that under React Strict Mode a field mounts,
89
+ * unmounts and mounts again, so a host that clears synchronously will lose the value
90
+ * on the second mount — clear on a later tick.
91
+ */
92
+ takeResult(field: {
93
+ propertyLabel: string;
94
+ sourceShapeIri?: string;
95
+ }): InstanceSuggestion[] | null;
96
+ }
97
+
98
+ /** One candidate for a relation field. */
99
+ export interface InstanceSuggestion {
100
+ id: string;
101
+ label: string;
102
+ image?: string;
103
+ }
104
+
105
+ export interface SearchInstancesOptions {
106
+ /** Free text; empty means "browse". */
107
+ query?: string;
108
+ limit?: number;
109
+ offset?: number;
110
+ /** Restrict to these ids — used when another field's value narrows the choices. */
111
+ narrowedIds?: string[] | null;
112
+ }
113
+
114
+ export interface DataManagerHost {
115
+ /**
116
+ * Base IRI for minting new instance IRIs.
117
+ *
118
+ * In Create Now this is the project's `dataRoot` — the ONE value in the catalog path
119
+ * that comes from cn-main rather than the app's own dataset. A standalone app knows its
120
+ * own domain and passes it from config, which is why this is a plain value here and not
121
+ * a lookup.
122
+ */
123
+ dataRoot?: string;
124
+
125
+ /**
126
+ * The metadata for ONE shape, by IRI.
127
+ *
128
+ * The only required member, and deliberately the smallest one that works. It used to ask
129
+ * for the whole catalog, which read as though a consumer needed a catalogue query before
130
+ * they could render anything — and that is not what the components do with it. There are
131
+ * exactly two uses: resolving the shape a relation field is searching, and resolving the
132
+ * shape a form is writing to. Both want one shape.
133
+ *
134
+ * So an app that composes a page for `Contact` returns its `Contact` shape from a constant
135
+ * and is done. An app that does have a catalog answers from it. Neither is asked to build
136
+ * a shape browser in order to render one table.
137
+ *
138
+ * Return `undefined` for an unknown IRI: a search then yields nothing rather than
139
+ * guessing, and a write is refused rather than putting untyped triples in the graph.
140
+ */
141
+ resolveShape(shapeIri: string): Promise<NodeShapeWire | undefined>;
142
+
143
+ /**
144
+ * The whole catalog, when the host happens to have one.
145
+ *
146
+ * Optional, and only a convenience: supplying it means `resolveShape` can be derived, so a
147
+ * host with a catalog need not write both.
148
+ */
149
+ resolveCatalog?(): Promise<Record<string, NodeShapeWire>>;
150
+
151
+ /**
152
+ * Find instances of a shape, for a relation field's picker.
153
+ *
154
+ * A host concern because *how* you search is host-specific: Create Now asks its backend
155
+ * on behalf of a project, a standalone app queries its own dataset. What the picker needs
156
+ * back is the same either way — id, a human label, optionally an image.
157
+ */
158
+ searchInstances?(
159
+ shapeIri: string,
160
+ options?: SearchInstancesOptions,
161
+ ): Promise<{results: InstanceSuggestion[]; hasMore: boolean}>;
162
+
163
+ /**
164
+ * Render a form for creating a related instance inline, without leaving the field.
165
+ *
166
+ * A render slot rather than a component reference on purpose. The relation picker used to
167
+ * `import AddInstanceForms` directly — a molecule importing an organism, which is a cycle
168
+ * and also the single thing that made the picker unextractable. The host decides what
169
+ * "create one of these" looks like; the picker only decides when to ask.
170
+ */
171
+ inlineCreate?(args: {
172
+ shapeIri: string;
173
+ onCreated: (created: InstanceSuggestion) => void;
174
+ onCancel: () => void;
175
+ }): ReactNode;
176
+
177
+ /**
178
+ * Read one instance's values.
179
+ *
180
+ * Used by the form's dependency cascade, which needs a parent instance's values to narrow
181
+ * a child field's choices. On the host because the read is host-specific; the cascade
182
+ * logic is not.
183
+ */
184
+ readInstance?(
185
+ shapeIri: string,
186
+ instanceId: string,
187
+ ): Promise<{rows: Record<string, unknown>[]}>;
188
+
189
+ navigate?: DataManagerNavigation;
190
+ picking?: DataManagerPicking;
191
+
192
+ /**
193
+ * Create an instance and return its IRI.
194
+ *
195
+ * Optional, and filled in from the DSL when absent — like `searchInstances`, and for the
196
+ * same reason: a form that cannot save unless the host wrote a create by hand is not
197
+ * "works standalone". Create Now overrides it only because its create also has to write
198
+ * a draft and record a projection edit, neither of which is a write concern.
199
+ */
200
+ createInstance?(
201
+ shapeIri: string,
202
+ values: Record<string, unknown>,
203
+ ): Promise<{id: string}>;
204
+
205
+ /** Update an instance. Optional, and filled in from the DSL when absent. */
206
+ updateInstance?(
207
+ shapeIri: string,
208
+ instanceId: string,
209
+ values: Record<string, unknown>,
210
+ ): Promise<void>;
211
+ }
212
+
213
+ /**
214
+ * A host with nothing wired up: catalog is empty, navigation is a no-op.
215
+ *
216
+ * For tests and for a first render before the real host exists. Deliberately silent
217
+ * rather than throwing — a missing host should render an empty table, not break the page
218
+ * that embedded it.
219
+ */
220
+ export function nullHost(): DataManagerHost {
221
+ return {
222
+ async resolveShape() {
223
+ return undefined;
224
+ },
225
+ };
226
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The host, supplied by context rather than threaded as props.
3
+ *
4
+ * The components that need the host are three levels down — a form renders a field, which
5
+ * renders a value editor, which renders the relation picker — and the picker is the one
6
+ * that needs to search, navigate and offer inline creation. Passing four callbacks through
7
+ * every intermediate component would put host concerns in the signature of components that
8
+ * have no interest in them.
9
+ *
10
+ * So: one provider at the top, `useDataManagerHost()` where it is needed. The default is
11
+ * `nullHost()`, which does nothing rather than throwing — a component rendered without a
12
+ * provider should show an empty picker, not break the page that embedded it.
13
+ */
14
+
15
+ import {createContext, createElement, useContext, useMemo, type ReactNode} from 'react';
16
+ import {nullHost, type DataManagerHost} from './host.js';
17
+ import {registerRuntimeShapes} from '@_linked/core/shapes/registerRuntimeShape';
18
+ import {narrowSuggestions, searchInstancesWithDsl} from './search.js';
19
+ import {createInstanceWithDsl, updateInstanceWithDsl} from './write.js';
20
+
21
+ const HostContext = createContext<DataManagerHost>(nullHost());
22
+
23
+ export interface DataManagerHostProviderProps {
24
+ host: DataManagerHost;
25
+ children?: ReactNode;
26
+ }
27
+
28
+ export function DataManagerHostProvider({
29
+ host,
30
+ children,
31
+ }: DataManagerHostProviderProps) {
32
+ return createElement(HostContext.Provider, {value: host}, children);
33
+ }
34
+
35
+ /**
36
+ * The host for the surrounding provider, with the defaults filled in.
37
+ *
38
+ * `searchInstances`, `createInstance` and `updateInstance` are supplied from the DSL when
39
+ * the host does not provide them, so reading, searching and saving all work with no host
40
+ * wiring at all. Callers therefore never branch on whether a method exists — which they
41
+ * previously had to, and which meant a host that forgot one rendered a silently empty
42
+ * picker or a form whose save button did nothing.
43
+ *
44
+ * Read and write are filled in from the same place on purpose. They are routed by
45
+ * `LinkedStorage` as a pair, and a read routed one way with a write routed another is how
46
+ * a form saves into a dataset the table is not looking at.
47
+ */
48
+ export function useDataManagerHost(): DataManagerHost {
49
+ const host = useContext(HostContext);
50
+
51
+ return useMemo<DataManagerHost>(() => {
52
+ const filled: DataManagerHost = {...host};
53
+
54
+ // Resolve one shape, and register it on the way through.
55
+ //
56
+ // `SelectBuilder.from(shapeIri)` resolves the IRI against core's shape registry, so a
57
+ // shape that was never registered produces an EMPTY result rather than an error — a table
58
+ // that silently shows nothing, indistinguishable from a shape with no instances. Asking
59
+ // the host to remember `registerRuntimeShapes` would make that the first thing every
60
+ // consumer gets wrong, so it happens here. Registration is idempotent and never shadows a
61
+ // compiled class.
62
+ //
63
+ // A host that supplied only `resolveCatalog` is served from it, so having a catalog does
64
+ // not mean writing the single-shape lookup as well.
65
+ const resolveShape = async (shapeIri: string) => {
66
+ const shape = host.resolveShape
67
+ ? await host.resolveShape(shapeIri)
68
+ : (await host.resolveCatalog?.())?.[shapeIri];
69
+ if (shape) registerRuntimeShapes([shape]);
70
+ return shape;
71
+ };
72
+ filled.resolveShape = resolveShape;
73
+
74
+ /** The shape, or a thrown error naming the IRI — a write must not guess. */
75
+ const requireShape = async (shapeIri: string) => {
76
+ const shape = await resolveShape(shapeIri);
77
+ if (!shape) {
78
+ throw new Error(
79
+ `Cannot write to ${shapeIri}: it is not in the catalog this host resolved. ` +
80
+ 'A write against an unknown shape would put untyped triples in the graph.',
81
+ );
82
+ }
83
+ return shape;
84
+ };
85
+
86
+ if (!filled.searchInstances) {
87
+ filled.searchInstances = async (shapeIri, options) => {
88
+ const shape = await resolveShape(shapeIri);
89
+ // No shape means no label property and no idea what to project. An empty result is
90
+ // the honest answer; guessing a projection would produce a wrong query.
91
+ if (!shape) return {results: [], hasMore: false};
92
+ const found = await searchInstancesWithDsl(shape, options);
93
+ return {
94
+ ...found,
95
+ results: narrowSuggestions(found.results, options?.narrowedIds),
96
+ };
97
+ };
98
+ }
99
+
100
+ if (!filled.createInstance) {
101
+ filled.createInstance = async (shapeIri, values) =>
102
+ createInstanceWithDsl(await requireShape(shapeIri), values, {
103
+ dataRoot: host.dataRoot,
104
+ });
105
+ }
106
+
107
+ if (!filled.updateInstance) {
108
+ filled.updateInstance = async (shapeIri, instanceId, values) =>
109
+ updateInstanceWithDsl(await requireShape(shapeIri), instanceId, values);
110
+ }
111
+
112
+ return filled;
113
+ }, [host]);
114
+ }
package/src/index.ts ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * `@_linked/shape-ui` — UI driven by a shape.
3
+ *
4
+ * Two halves that grew separately and belong together.
5
+ *
6
+ * The **editors** take a shape and a property and render one control — a text field, a select,
7
+ * a date picker. Import them by path: `@_linked/shape-ui/components/TextfieldEditor`.
8
+ *
9
+ * The **surfaces** are what you build out of those: a table over any shape's instances, a form
10
+ * derived from its property list, a read-only view, a relation picker, and the shape domain
11
+ * underneath — column derivation, property visibility, validation, and the read and write paths
12
+ * through the Linked Query DSL.
13
+ *
14
+ * The surfaces arrived from `@create-now/data-manager`, where they were private on the
15
+ * assumption that shape-driven CRUD was a product. It is not: it renders any shape, knows
16
+ * nothing about projects, and is worth nothing without shapes to render. The product is the
17
+ * studio that authors them. See arch-03 §UI package layering.
18
+ *
19
+ * `./package.js` is imported below, as in every other `@_linked/*` package, so this package
20
+ * registers itself — the house pattern, nothing special here. It briefly had to be omitted:
21
+ * it triggered "Class extends value undefined is not a constructor or null" from inside a
22
+ * query. That was never this package's fault — it was a live-binding bug in core's
23
+ * `ShapeClass`, which captured `Shape` by named import before `Shape.js` had finished
24
+ * evaluating, so the runtime subclass extended `undefined`. Fixed in `@_linked/core` 2.18.1
25
+ * (linked-cm/core#220), which is why this package depends on `^2.18.1`.
26
+ *
27
+ * The empty ontology this package scaffolded from the template is gone: one export, no terms,
28
+ * and a namespace on a domain we no longer use. Nothing referenced it.
29
+ */
30
+
31
+ // Registers this package with the linked package system. The only side effect in this barrel.
32
+ import './package.js';
33
+
34
+
35
+ export type {
36
+ DataManagerHost,
37
+ DataManagerNavigation,
38
+ DataManagerPicking,
39
+ PickRequest,
40
+ InstanceSuggestion,
41
+ SearchInstancesOptions,
42
+ } from './host.js';
43
+ export {nullHost} from './host.js';
44
+
45
+ export type {DataManagerHostProviderProps} from './hostContext.js';
46
+ export {DataManagerHostProvider, useDataManagerHost} from './hostContext.js';
47
+
48
+ export type {InstanceQuery, InstanceRows} from './read.js';
49
+ export {selectInstance, selectInstances} from './read.js';
50
+
51
+ export type {InstanceValues} from './write.js';
52
+ export {
53
+ createInstanceWithDsl,
54
+ mintInstanceIri,
55
+ updateInstanceWithDsl,
56
+ } from './write.js';
57
+
58
+ export type {Column, DisplayContext, TableMode} from './columns.js';
59
+ export {
60
+ columnsFor,
61
+ displayableProperties,
62
+ hasDeclaredRanks,
63
+ labelProperty,
64
+ } from './columns.js';
65
+
66
+ export {narrowSuggestions, searchInstancesWithDsl} from './search.js';
67
+
68
+ // ─── Components ──────────────────────────────────────────────────────────────
69
+ //
70
+ // The shape-driven CRUD surfaces. Each one takes shape metadata and instance data and asks
71
+ // the host for anything that is specific to the application around it — where a click leads,
72
+ // where an IRI is minted, how a picker is opened.
73
+ export {default as InstanceOverview} from './components/InstanceOverview.js';
74
+ export type {BatchResult} from './components/InstanceOverview.js';
75
+ export {default as ReactTable} from './components/ReactTable.js';
76
+ export type {ReactTableProps} from './components/ReactTable.js';
77
+ export {InstanceView} from './components/InstanceView.js';
78
+ export {default as AddInstanceForms} from './components/AddInstanceForms.js';
79
+ export {default as EditInstanceForms} from './components/EditInstanceForms.js';
80
+ export {InstanceDeletionDialog} from './components/InstanceDeletionDialog.js';
81
+ export {default as BatchEditDrawer} from './components/BatchEditDrawer.js';
82
+ export {default as CustomMultiSelect} from './components/CustomMultiSelect.js';
83
+ export {DynamicForm} from './components/DynamicFormField.js';
84
+ export {FormField} from './components/FormField.js';
85
+ export {default as NodeValuesEditor} from './components/NodeValuesEditor.js';
86
+ export {NodeBadge} from './components/NodeBadge.js';
87
+ export {FilterBadge} from './components/FilterBadge.js';
88
+
89
+ // ─── Shape domain ────────────────────────────────────────────────────────────
90
+ export * from './shape/contracts.js';
91
+ export * from './shape/naming.js';
92
+ export * from './shape/nodeDisplay.js';
93
+ export * from './shape/propertyVisibility.js';
package/src/package.ts ADDED
@@ -0,0 +1,21 @@
1
+ import {
2
+ getPackageShape,
3
+ linkedOntology,
4
+ linkedShape,
5
+ linkedUtil,
6
+ packageExports,
7
+ registerPackageExport,
8
+ registerPackageModule,
9
+ } from '@_linked/core/package';
10
+
11
+ export const { packageName } = { packageName: '@_linked/ui' };
12
+
13
+ export {
14
+ getPackageShape,
15
+ linkedOntology,
16
+ linkedShape,
17
+ linkedUtil,
18
+ packageExports,
19
+ registerPackageExport,
20
+ registerPackageModule,
21
+ };