@narrative.io/jsonforms-provider-protocols 2.11.0-beta.0 → 2.12.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 (148) hide show
  1. package/README.md +193 -33
  2. package/dist/core/initFormData.d.ts +17 -0
  3. package/dist/core/initFormData.d.ts.map +1 -0
  4. package/dist/core/initFormData.js +99 -0
  5. package/dist/core/initFormData.js.map +1 -0
  6. package/dist/core/projection.d.ts +36 -0
  7. package/dist/core/projection.d.ts.map +1 -0
  8. package/dist/core/projection.js +77 -0
  9. package/dist/core/projection.js.map +1 -0
  10. package/dist/core/refs.d.ts +58 -0
  11. package/dist/core/refs.d.ts.map +1 -0
  12. package/dist/core/refs.js +70 -0
  13. package/dist/core/refs.js.map +1 -0
  14. package/dist/core/resolveScope.d.ts +17 -0
  15. package/dist/core/resolveScope.d.ts.map +1 -0
  16. package/dist/core/resolveScope.js +28 -0
  17. package/dist/core/resolveScope.js.map +1 -0
  18. package/dist/core/seedProjectionTargets.d.ts +60 -0
  19. package/dist/core/seedProjectionTargets.d.ts.map +1 -0
  20. package/dist/core/seedProjectionTargets.js +52 -0
  21. package/dist/core/seedProjectionTargets.js.map +1 -0
  22. package/dist/core/transforms.d.ts +8 -10
  23. package/dist/core/transforms.d.ts.map +1 -1
  24. package/dist/core/transforms.js +58 -13
  25. package/dist/core/transforms.js.map +1 -1
  26. package/dist/core/types.d.ts +8 -0
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/index.d.ts +9 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +21 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/jsonforms-provider-protocols.css +6 -2
  33. package/dist/no-eval-ajv.d.ts +70 -0
  34. package/dist/no-eval-ajv.d.ts.map +1 -0
  35. package/dist/no-eval-ajv.js +247 -0
  36. package/dist/no-eval-ajv.js.map +1 -0
  37. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  38. package/dist/vue/components/ProviderAutocomplete.vue.js +10 -4
  39. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  40. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
  41. package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
  42. package/dist/vue/components/ProviderMultiSelect.vue2.js +19 -9
  43. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
  44. package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts +9 -0
  45. package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts.map +1 -0
  46. package/dist/vue/components/ProviderObjectMultiSelect.vue.js +8 -0
  47. package/dist/vue/components/ProviderObjectMultiSelect.vue.js.map +1 -0
  48. package/dist/vue/components/ProviderObjectMultiSelect.vue2.js +142 -0
  49. package/dist/vue/components/ProviderObjectMultiSelect.vue2.js.map +1 -0
  50. package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
  51. package/dist/vue/components/ProviderSelect.vue.js +1 -1
  52. package/dist/vue/components/ProviderSelect.vue2.js +20 -8
  53. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  54. package/dist/vue/composables/useDataLayer.d.ts +10 -0
  55. package/dist/vue/composables/useDataLayer.d.ts.map +1 -0
  56. package/dist/vue/composables/useDataLayer.js +26 -0
  57. package/dist/vue/composables/useDataLayer.js.map +1 -0
  58. package/dist/vue/composables/useDerive.d.ts +5 -2
  59. package/dist/vue/composables/useDerive.d.ts.map +1 -1
  60. package/dist/vue/composables/useDerive.js +29 -12
  61. package/dist/vue/composables/useDerive.js.map +1 -1
  62. package/dist/vue/composables/useDeriveInitialValue.d.ts +36 -0
  63. package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
  64. package/dist/vue/composables/useDeriveInitialValue.js +125 -0
  65. package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
  66. package/dist/vue/composables/useDirtyValidation.d.ts +9 -0
  67. package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -0
  68. package/dist/vue/composables/useDirtyValidation.js +15 -0
  69. package/dist/vue/composables/useDirtyValidation.js.map +1 -0
  70. package/dist/vue/composables/useProjection.d.ts +42 -0
  71. package/dist/vue/composables/useProjection.d.ts.map +1 -0
  72. package/dist/vue/composables/useProjection.js +116 -0
  73. package/dist/vue/composables/useProjection.js.map +1 -0
  74. package/dist/vue/composables/useProvider.d.ts +2 -2
  75. package/dist/vue/composables/useProvider.d.ts.map +1 -1
  76. package/dist/vue/composables/useProvider.js +14 -10
  77. package/dist/vue/composables/useProvider.js.map +1 -1
  78. package/dist/vue/index.d.ts +9 -1
  79. package/dist/vue/index.d.ts.map +1 -1
  80. package/dist/vue/index.js +72 -34
  81. package/dist/vue/index.js.map +1 -1
  82. package/dist/vue/primevue/JfBoolean.vue.d.ts +9 -0
  83. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
  84. package/dist/vue/primevue/JfBoolean.vue.js +42 -21
  85. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
  86. package/dist/vue/primevue/JfEnum.vue.d.ts +9 -0
  87. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  88. package/dist/vue/primevue/JfEnum.vue.js +35 -21
  89. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  90. package/dist/vue/primevue/JfEnumArray.vue.d.ts +9 -0
  91. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  92. package/dist/vue/primevue/JfEnumArray.vue.js +37 -17
  93. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  94. package/dist/vue/primevue/JfNumber.vue.d.ts +9 -0
  95. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  96. package/dist/vue/primevue/JfNumber.vue.js +30 -20
  97. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  98. package/dist/vue/primevue/JfText.vue.d.ts +9 -0
  99. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  100. package/dist/vue/primevue/JfText.vue.js +48 -32
  101. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  102. package/dist/vue/primevue/JfTextArea.vue.d.ts +9 -0
  103. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  104. package/dist/vue/primevue/JfTextArea.vue.js +31 -16
  105. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  106. package/dist/vue/primevue/index.d.ts.map +1 -1
  107. package/dist/vue/primevue/index.js +74 -7
  108. package/dist/vue/primevue/index.js.map +1 -1
  109. package/dist/vue/utils/autoSelect.js.map +1 -1
  110. package/dist/vue/utils/objectMultiSelect.d.ts +68 -0
  111. package/dist/vue/utils/objectMultiSelect.d.ts.map +1 -0
  112. package/dist/vue/utils/objectMultiSelect.js +72 -0
  113. package/dist/vue/utils/objectMultiSelect.js.map +1 -0
  114. package/dist/vue/utils/placeholder.d.ts +17 -0
  115. package/dist/vue/utils/placeholder.d.ts.map +1 -0
  116. package/dist/vue/utils/placeholder.js +17 -0
  117. package/dist/vue/utils/placeholder.js.map +1 -0
  118. package/package.json +10 -2
  119. package/src/core/initFormData.ts +208 -0
  120. package/src/core/projection.ts +147 -0
  121. package/src/core/refs.ts +166 -0
  122. package/src/core/resolveScope.ts +54 -0
  123. package/src/core/seedProjectionTargets.ts +144 -0
  124. package/src/core/transforms.ts +118 -26
  125. package/src/core/types.ts +9 -0
  126. package/src/index.ts +22 -2
  127. package/src/no-eval-ajv.ts +381 -0
  128. package/src/vue/components/ProviderAutocomplete.vue +10 -6
  129. package/src/vue/components/ProviderMultiSelect.vue +22 -15
  130. package/src/vue/components/ProviderObjectMultiSelect.vue +169 -0
  131. package/src/vue/components/ProviderSelect.vue +23 -14
  132. package/src/vue/composables/useDataLayer.ts +43 -0
  133. package/src/vue/composables/useDerive.ts +62 -16
  134. package/src/vue/composables/useDeriveInitialValue.ts +195 -0
  135. package/src/vue/composables/useDirtyValidation.ts +20 -0
  136. package/src/vue/composables/useProjection.ts +245 -0
  137. package/src/vue/composables/useProvider.ts +28 -11
  138. package/src/vue/index.ts +83 -47
  139. package/src/vue/primevue/JfBoolean.vue +35 -12
  140. package/src/vue/primevue/JfEnum.vue +34 -25
  141. package/src/vue/primevue/JfEnumArray.vue +36 -19
  142. package/src/vue/primevue/JfNumber.vue +30 -22
  143. package/src/vue/primevue/JfText.vue +46 -31
  144. package/src/vue/primevue/JfTextArea.vue +30 -19
  145. package/src/vue/primevue/index.ts +88 -7
  146. package/src/vue/utils/autoSelect.ts +2 -2
  147. package/src/vue/utils/objectMultiSelect.ts +171 -0
  148. package/src/vue/utils/placeholder.ts +42 -0
@@ -4,6 +4,8 @@ import JfNumber from "./JfNumber.vue";
4
4
  import JfEnum from "./JfEnum.vue";
5
5
  import JfEnumArray from "./JfEnumArray.vue";
6
6
  import JfBoolean from "./JfBoolean.vue";
7
+ import { getProjectedSchema } from "../../core/projection";
8
+ import { resolveScopeSchema } from "../../core/resolveScope";
7
9
 
8
10
  // Auto-inject layout styles
9
11
  const injectLayoutStyles = () => {
@@ -79,6 +81,7 @@ export function registerPrimevueRenderers(jsonformsCore: any): unknown[] {
79
81
  isNumberControl,
80
82
  isIntegerControl,
81
83
  and,
84
+ or,
82
85
  isControl,
83
86
  schemaMatches,
84
87
  isBooleanControl,
@@ -122,27 +125,105 @@ export function registerPrimevueRenderers(jsonformsCore: any): unknown[] {
122
125
  );
123
126
  };
124
127
 
128
+ // Projection-aware schema check: when options.projection is set,
129
+ // resolve the projected schema and test against it instead of the original
130
+
131
+ const projectedSchemaMatches =
132
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
+ (check: (schema: any) => boolean) =>
134
+ (uischema: unknown, schema: unknown): boolean => {
135
+ const ui = uischema as {
136
+ type?: string;
137
+ scope?: string;
138
+ options?: { projection?: string };
139
+ };
140
+ const projection = ui?.options?.projection;
141
+ if (!projection || ui?.type !== "Control" || !ui?.scope) return false;
142
+
143
+ const propertySchema = resolveScopeSchema(
144
+ ui.scope,
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ schema as Record<string, any>,
147
+ );
148
+ if (!propertySchema) return false;
149
+
150
+ return check(getProjectedSchema(propertySchema, projection));
151
+ };
152
+
153
+ const isMultilineProjection = (uischema: unknown, schema: unknown) => {
154
+ const ui = uischema as { options?: { multi?: boolean } };
155
+ return (
156
+ ui?.options?.multi === true &&
157
+ projectedSchemaMatches((s) => s?.type === "string")(uischema, schema)
158
+ );
159
+ };
160
+
125
161
  const renderers = [
126
162
  // Multiline text has higher priority than regular text
127
- { tester: rankWith(PRIME + 4, isMultilineString), renderer: JfTextArea },
128
- { tester: rankWith(PRIME + 3, isStringControl), renderer: JfText },
129
163
  {
130
- tester: rankWith(PRIME + 6, isIntegerControl),
164
+ tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)),
165
+ renderer: JfTextArea,
166
+ },
167
+ {
168
+ tester: rankWith(
169
+ PRIME + 3,
170
+ or(
171
+ isStringControl,
172
+ projectedSchemaMatches((s) => s?.type === "string"),
173
+ ),
174
+ ),
175
+ renderer: JfText,
176
+ },
177
+ {
178
+ tester: rankWith(
179
+ PRIME + 6,
180
+ or(
181
+ isIntegerControl,
182
+ projectedSchemaMatches((s) => s?.type === "integer"),
183
+ ),
184
+ ),
131
185
  renderer: JfNumber,
132
186
  },
133
187
  {
134
- tester: rankWith(PRIME + 4, isNumberControl),
188
+ tester: rankWith(
189
+ PRIME + 4,
190
+ or(
191
+ isNumberControl,
192
+ projectedSchemaMatches((s) => s?.type === "number"),
193
+ ),
194
+ ),
135
195
  renderer: JfNumber,
136
196
  },
137
197
  {
138
- tester: rankWith(PRIME + 7, and(isControl, schemaMatches(isScalarEnum))),
198
+ tester: rankWith(
199
+ PRIME + 7,
200
+ or(
201
+ and(isControl, schemaMatches(isScalarEnum)),
202
+ and(isControl, projectedSchemaMatches(isScalarEnum)),
203
+ ),
204
+ ),
139
205
  renderer: JfEnum,
140
206
  },
141
207
  {
142
- tester: rankWith(PRIME + 8, and(isControl, schemaMatches(isEnumArray))),
208
+ tester: rankWith(
209
+ PRIME + 8,
210
+ or(
211
+ and(isControl, schemaMatches(isEnumArray)),
212
+ and(isControl, projectedSchemaMatches(isEnumArray)),
213
+ ),
214
+ ),
143
215
  renderer: JfEnumArray,
144
216
  },
145
- { tester: rankWith(PRIME + 3, isBooleanControl), renderer: JfBoolean },
217
+ {
218
+ tester: rankWith(
219
+ PRIME + 3,
220
+ or(
221
+ isBooleanControl,
222
+ projectedSchemaMatches((s) => s?.type === "boolean"),
223
+ ),
224
+ ),
225
+ renderer: JfBoolean,
226
+ },
146
227
  ];
147
228
 
148
229
  // Update the exported array
@@ -57,7 +57,7 @@ export interface AutoSelectMultiParams {
57
57
  * - Current value is empty array OR current selection is not in the current options
58
58
  */
59
59
  export function shouldAutoSelectMulti(
60
- params: AutoSelectMultiParams
60
+ params: AutoSelectMultiParams,
61
61
  ): unknown[] | null {
62
62
  const { autoSelectSingle, isLoading, items, currentValue } = params;
63
63
 
@@ -73,7 +73,7 @@ export function shouldAutoSelectMulti(
73
73
  const currentArray = Array.isArray(currentValue) ? currentValue : [];
74
74
  const isValueEmpty = currentArray.length === 0;
75
75
  const hasValidSelection = currentArray.some((val) =>
76
- items.some((item) => item.value === val)
76
+ items.some((item) => item.value === val),
77
77
  );
78
78
 
79
79
  if (isValueEmpty || !hasValidSelection) {
@@ -0,0 +1,171 @@
1
+ import { deref } from "../../core/refs";
2
+
3
+ /**
4
+ * Helpers for `ProviderObjectMultiSelect`: bidirectional translation between
5
+ * the form-data shape (paired objects with consumer-named keys) and the
6
+ * MultiSelect model shape (`{ value, label }` matching the provider's `map`
7
+ * config), plus `objectKeys` inference when the consumer doesn't specify
8
+ * them on the uischema.
9
+ *
10
+ * Pure functions — no Vue, no rendering — so the renderer can compose them
11
+ * and tests can exercise the logic in isolation.
12
+ */
13
+
14
+ export interface ObjectKeys {
15
+ /** Property name on the form-data object that holds the identifier. */
16
+ value: string;
17
+ /** Property name on the form-data object that holds the display string. */
18
+ label: string;
19
+ }
20
+
21
+ export interface MultiSelectOption {
22
+ value: unknown;
23
+ label: string;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ /**
28
+ * Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the
29
+ * shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items
30
+ * already in `{ value, label }` shape pass through; missing keys yield
31
+ * `undefined` / `""` so the renderer doesn't blow up on partial data.
32
+ */
33
+ export function toMultiSelectShape(
34
+ formData: unknown,
35
+ keys: ObjectKeys,
36
+ ): MultiSelectOption[] {
37
+ if (!Array.isArray(formData)) return [];
38
+ return formData
39
+ .filter((item) => item !== null && typeof item === "object")
40
+ .map((item) => {
41
+ const obj = item as Record<string, unknown>;
42
+ // Tolerate already-translated items: if both `value` and `label` are
43
+ // present and the form-data keys aren't, assume MultiSelect shape.
44
+ if (
45
+ !(keys.value in obj) &&
46
+ !(keys.label in obj) &&
47
+ "value" in obj &&
48
+ "label" in obj
49
+ ) {
50
+ return obj as MultiSelectOption;
51
+ }
52
+ return {
53
+ value: obj[keys.value],
54
+ label: typeof obj[keys.label] === "string" ? (obj[keys.label] as string) : "",
55
+ };
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items
61
+ * back into form-data shape using the consumer-specified property names.
62
+ */
63
+ export function fromMultiSelectShape(
64
+ modelData: unknown,
65
+ keys: ObjectKeys,
66
+ ): Record<string, unknown>[] {
67
+ if (!Array.isArray(modelData)) return [];
68
+ return modelData
69
+ .filter((item) => item !== null && typeof item === "object")
70
+ .map((item) => {
71
+ const obj = item as Record<string, unknown>;
72
+ return {
73
+ [keys.value]: obj.value,
74
+ [keys.label]: obj.label,
75
+ };
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Order-insensitive equality of two object-shape selections by the
81
+ * identifier property. Used to short-circuit redundant `handleChange`
82
+ * calls when the model emits the same selection under reference inequality.
83
+ */
84
+ export function sameObjectSet(
85
+ a: unknown,
86
+ b: unknown,
87
+ identifierKey: string,
88
+ ): boolean {
89
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
90
+ return false;
91
+ }
92
+ const ids = new Set(
93
+ b
94
+ .filter((x) => x !== null && typeof x === "object")
95
+ .map((x) => (x as Record<string, unknown>)[identifierKey]),
96
+ );
97
+ return a.every((x) => {
98
+ if (x === null || typeof x !== "object") return false;
99
+ return ids.has((x as Record<string, unknown>)[identifierKey]);
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Resolve the items schema of an array control, dereferencing `items.$ref`
105
+ * against the root if present. Returns `undefined` if no items schema exists.
106
+ */
107
+ export function resolveItemsSchema(
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ arraySchema: Record<string, any> | undefined,
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
+ rootSchema: Record<string, any>,
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ ): Record<string, any> | undefined {
114
+ if (!arraySchema || typeof arraySchema !== "object") return undefined;
115
+ const items = arraySchema.items;
116
+ if (!items || typeof items !== "object" || Array.isArray(items)) {
117
+ return undefined;
118
+ }
119
+ return deref(items, rootSchema);
120
+ }
121
+
122
+ /**
123
+ * Infer `objectKeys` from a resolved items schema when the consumer hasn't
124
+ * specified them on the uischema.
125
+ *
126
+ * Strategy: look at `items.required`. If it has exactly two entries, the
127
+ * one whose property has `format: 'uuid'` becomes `value`; the other
128
+ * becomes `label`. If neither has a uuid format, the first entry is
129
+ * `value`, the second is `label`. Returns `undefined` (and the renderer
130
+ * throws at mount) for any other shape — explicit `objectKeys` is required.
131
+ */
132
+ export function inferObjectKeys(
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ itemsSchema: Record<string, any> | undefined,
135
+ ): ObjectKeys | undefined {
136
+ if (!itemsSchema || itemsSchema.type !== "object") return undefined;
137
+ const required = itemsSchema.required;
138
+ if (!Array.isArray(required) || required.length !== 2) return undefined;
139
+ const [a, b] = required as [string, string];
140
+ const props = (itemsSchema.properties ?? {}) as Record<
141
+ string,
142
+ { format?: string }
143
+ >;
144
+ const aIsUuid = props[a]?.format === "uuid";
145
+ const bIsUuid = props[b]?.format === "uuid";
146
+ if (aIsUuid && !bIsUuid) return { value: a, label: b };
147
+ if (bIsUuid && !aIsUuid) return { value: b, label: a };
148
+ return { value: a, label: b };
149
+ }
150
+
151
+ /**
152
+ * Resolve the active `objectKeys` for a control: prefer the explicit
153
+ * `uischema.options.objectKeys`, fall back to schema-driven inference.
154
+ * Returns `undefined` when neither is available; the renderer surfaces a
155
+ * runtime error in that case so the consumer knows to be explicit.
156
+ */
157
+ export function resolveObjectKeys(
158
+ uischemaOptions: { objectKeys?: { value?: unknown; label?: unknown } } | undefined,
159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
160
+ itemsSchema: Record<string, any> | undefined,
161
+ ): ObjectKeys | undefined {
162
+ const explicit = uischemaOptions?.objectKeys;
163
+ if (
164
+ explicit &&
165
+ typeof explicit.value === "string" &&
166
+ typeof explicit.label === "string"
167
+ ) {
168
+ return { value: explicit.value, label: explicit.label };
169
+ }
170
+ return inferObjectKeys(itemsSchema);
171
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Placeholder resolution for form controls.
3
+ *
4
+ * Precedence:
5
+ * 1. `uischema.options.placeholder` (explicit author intent — always wins)
6
+ * 2. `Select ${label}` or `Enter ${label}` when a label is resolvable
7
+ * 3. Kind-appropriate bare fallback
8
+ *
9
+ * Never falls back to `schema.description` — descriptions are rendered as
10
+ * prose above the field by our renderers, so re-using them as placeholder
11
+ * produces a duplicated, truncated string inside the input.
12
+ */
13
+
14
+ export type PlaceholderKind = "select" | "input";
15
+
16
+ /**
17
+ * Strip a trailing required-indicator asterisk (" *" or "*") that `resolveLabel`
18
+ * appends for required fields, so composed placeholders read naturally.
19
+ */
20
+ function stripRequiredMarker(label: string): string {
21
+ return label.replace(/\s*\*\s*$/, "").trim();
22
+ }
23
+
24
+ export function resolvePlaceholder(
25
+ uischema: { options?: unknown } | undefined,
26
+ resolvedLabel: string | undefined,
27
+ kind: PlaceholderKind,
28
+ ): string | undefined {
29
+ const options = uischema?.options;
30
+ const explicit =
31
+ options && typeof options === "object"
32
+ ? (options as Record<string, unknown>).placeholder
33
+ : undefined;
34
+ if (typeof explicit === "string" && explicit.length > 0) return explicit;
35
+
36
+ const label = resolvedLabel ? stripRequiredMarker(resolvedLabel) : "";
37
+ if (label) {
38
+ return kind === "select" ? `Select ${label}` : `Enter ${label}`;
39
+ }
40
+
41
+ return kind === "select" ? "Select…" : undefined;
42
+ }