@narrative.io/jsonforms-provider-protocols 3.0.0-beta.14 → 3.0.0-beta.17

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 (59) hide show
  1. package/dist/core/initFormData.js +1 -24
  2. package/dist/core/initFormData.js.map +1 -1
  3. package/dist/core/projection.d.ts +4 -0
  4. package/dist/core/projection.d.ts.map +1 -1
  5. package/dist/core/projection.js +4 -1
  6. package/dist/core/projection.js.map +1 -1
  7. package/dist/core/refs.d.ts +42 -0
  8. package/dist/core/refs.d.ts.map +1 -0
  9. package/dist/core/refs.js +53 -0
  10. package/dist/core/refs.js.map +1 -0
  11. package/dist/core/resolveScope.d.ts +6 -0
  12. package/dist/core/resolveScope.d.ts.map +1 -1
  13. package/dist/core/resolveScope.js +4 -2
  14. package/dist/core/resolveScope.js.map +1 -1
  15. package/dist/jsonforms-provider-protocols.css +2 -2
  16. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  17. package/dist/vue/components/ProviderAutocomplete.vue.js +6 -5
  18. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  19. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
  20. package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
  21. package/dist/vue/components/ProviderMultiSelect.vue2.js +12 -7
  22. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
  23. package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
  24. package/dist/vue/components/ProviderSelect.vue.js +1 -1
  25. package/dist/vue/components/ProviderSelect.vue2.js +12 -7
  26. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  27. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  28. package/dist/vue/primevue/JfEnum.vue.js +6 -1
  29. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  30. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  31. package/dist/vue/primevue/JfEnumArray.vue.js +6 -1
  32. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  33. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  34. package/dist/vue/primevue/JfNumber.vue.js +2 -1
  35. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  36. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  37. package/dist/vue/primevue/JfText.vue.js +10 -2
  38. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  39. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  40. package/dist/vue/primevue/JfTextArea.vue.js +2 -1
  41. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  42. package/dist/vue/utils/placeholder.d.ts +17 -0
  43. package/dist/vue/utils/placeholder.d.ts.map +1 -0
  44. package/dist/vue/utils/placeholder.js +17 -0
  45. package/dist/vue/utils/placeholder.js.map +1 -0
  46. package/package.json +1 -1
  47. package/src/core/initFormData.ts +1 -43
  48. package/src/core/projection.ts +10 -2
  49. package/src/core/refs.ts +114 -0
  50. package/src/core/resolveScope.ts +11 -2
  51. package/src/vue/components/ProviderAutocomplete.vue +6 -7
  52. package/src/vue/components/ProviderMultiSelect.vue +12 -12
  53. package/src/vue/components/ProviderSelect.vue +12 -12
  54. package/src/vue/primevue/JfEnum.vue +5 -3
  55. package/src/vue/primevue/JfEnumArray.vue +5 -3
  56. package/src/vue/primevue/JfNumber.vue +3 -2
  57. package/src/vue/primevue/JfText.vue +13 -5
  58. package/src/vue/primevue/JfTextArea.vue +3 -4
  59. package/src/vue/utils/placeholder.ts +42 -0
@@ -1,3 +1,5 @@
1
+ import { deepDeref, deref } from "./refs";
2
+
1
3
  /**
2
4
  * Resolve a JSON Forms scope path to its schema within a root schema.
3
5
  * Handles nested paths like "#/properties/parent/properties/child".
@@ -6,6 +8,12 @@
6
8
  * - "properties" segments navigate into object `.properties`
7
9
  * - "items" segments navigate into array `.items`
8
10
  * - all other segments index directly into the current object
11
+ *
12
+ * `$ref` nodes are dereferenced transparently at every step, so scopes that
13
+ * cross a `$ref` boundary (e.g. `items: { $ref: "#/$defs/X" }`) resolve to
14
+ * the target schema rather than returning `{}`. The returned schema is also
15
+ * deep-dereferenced so downstream walkers (e.g. `getProjectedSchema`) can
16
+ * operate on a self-contained sub-schema without needing the original root.
9
17
  */
10
18
  export function resolveScopeSchema(
11
19
  scope: string,
@@ -17,13 +25,14 @@ export function resolveScopeSchema(
17
25
 
18
26
  // Remove the leading "#/" and split into segments
19
27
  const path = scope.replace(/^#\/?/, "");
20
- if (!path) return rootSchema;
28
+ if (!path) return deepDeref(rootSchema, rootSchema);
21
29
 
22
30
  const segments = path.split("/");
23
31
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
32
  let current: any = rootSchema;
25
33
 
26
34
  for (const segment of segments) {
35
+ current = deref(current, rootSchema);
27
36
  if (!current || typeof current !== "object") return undefined;
28
37
 
29
38
  if (segment === "properties") {
@@ -35,5 +44,5 @@ export function resolveScopeSchema(
35
44
  }
36
45
  }
37
46
 
38
- return current;
47
+ return deepDeref(current, rootSchema);
39
48
  }
@@ -12,10 +12,11 @@ const props = defineProps<{
12
12
  path: string;
13
13
  }>();
14
14
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
15
- const { projectedData, handleProjectedChange: handleChange } = useProjection(
16
- control,
17
- rawHandleChange,
18
- );
15
+ const {
16
+ projectedData,
17
+ projectedLabel,
18
+ handleProjectedChange: handleChange,
19
+ } = useProjection(control, rawHandleChange);
19
20
 
20
21
  const binding = computed(() => {
21
22
  const provider = control.value.uischema?.options?.provider;
@@ -57,9 +58,7 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
57
58
 
58
59
  <template>
59
60
  <div class="jf-control">
60
- <label v-if="control.schema.title" class="jf-label">{{
61
- control.schema.title
62
- }}</label>
61
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
63
62
  <div v-if="control.description" class="jf-description">
64
63
  {{ control.description }}
65
64
  </div>
@@ -5,6 +5,7 @@ import { computed, inject, watch } from "vue";
5
5
  import { useProvider } from "../composables/useProvider";
6
6
  import { useProjection } from "../composables/useProjection";
7
7
  import { shouldAutoSelectMulti } from "../utils/autoSelect";
8
+ import { resolvePlaceholder } from "../utils/placeholder";
8
9
  import MultiSelect from "primevue/multiselect";
9
10
 
10
11
  const props = defineProps<{
@@ -13,10 +14,11 @@ const props = defineProps<{
13
14
  path: string;
14
15
  }>();
15
16
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
16
- const { projectedData, handleProjectedChange: handleChange } = useProjection(
17
- control,
18
- rawHandleChange,
19
- );
17
+ const {
18
+ projectedData,
19
+ projectedLabel,
20
+ handleProjectedChange: handleChange,
21
+ } = useProjection(control, rawHandleChange);
20
22
 
21
23
  const binding = computed(() => {
22
24
  const provider = control.value.uischema?.options?.provider;
@@ -94,19 +96,17 @@ const value = computed({
94
96
 
95
97
  const placeholder = computed(() => {
96
98
  if (loading.value) return "Loading…";
97
- // Check for placeholder in uischema options
98
- const uischemaPlaceholder = (
99
- control.value.uischema as { options?: { placeholder?: string } }
100
- )?.options?.placeholder;
101
- return uischemaPlaceholder || "Select…";
99
+ return resolvePlaceholder(
100
+ control.value.uischema,
101
+ projectedLabel.value,
102
+ "select",
103
+ );
102
104
  });
103
105
  </script>
104
106
 
105
107
  <template>
106
108
  <div class="jf-control">
107
- <label v-if="control.schema.title" class="jf-label">{{
108
- control.schema.title
109
- }}</label>
109
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
110
110
  <div v-if="control.description" class="jf-description">
111
111
  {{ control.description }}
112
112
  </div>
@@ -6,6 +6,7 @@ import { useProvider } from "../composables/useProvider";
6
6
  import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
7
7
  import { useProjection } from "../composables/useProjection";
8
8
  import { shouldAutoSelect } from "../utils/autoSelect";
9
+ import { resolvePlaceholder } from "../utils/placeholder";
9
10
  import Dropdown from "primevue/dropdown";
10
11
 
11
12
  const props = defineProps<{
@@ -14,10 +15,11 @@ const props = defineProps<{
14
15
  path: string;
15
16
  }>();
16
17
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
17
- const { projectedData, handleProjectedChange: handleChange } = useProjection(
18
- control,
19
- rawHandleChange,
20
- );
18
+ const {
19
+ projectedData,
20
+ projectedLabel,
21
+ handleProjectedChange: handleChange,
22
+ } = useProjection(control, rawHandleChange);
21
23
 
22
24
  const binding = computed(() => {
23
25
  const provider = control.value.uischema?.options?.provider;
@@ -79,19 +81,17 @@ const value = computed({
79
81
 
80
82
  const placeholder = computed(() => {
81
83
  if (loading.value) return "Loading…";
82
- // Check for placeholder in uischema options
83
- const uischemaPlaceholder = (
84
- control.value.uischema as { options?: { placeholder?: string } }
85
- )?.options?.placeholder;
86
- return uischemaPlaceholder || "Select…";
84
+ return resolvePlaceholder(
85
+ control.value.uischema,
86
+ projectedLabel.value,
87
+ "select",
88
+ );
87
89
  });
88
90
  </script>
89
91
 
90
92
  <template>
91
93
  <div class="jf-control">
92
- <label v-if="control.schema.title" class="jf-label">{{
93
- control.schema.title
94
- }}</label>
94
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
95
95
  <div v-if="control.description" class="jf-description">
96
96
  {{ control.description }}
97
97
  </div>
@@ -49,6 +49,7 @@ import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
49
49
  import { useProjection } from "../composables/useProjection";
50
50
  import { useDirtyValidation } from "../composables/useDirtyValidation";
51
51
  import { shouldAutoSelect } from "../utils/autoSelect";
52
+ import { resolvePlaceholder } from "../utils/placeholder";
52
53
  import Dropdown from "primevue/dropdown";
53
54
 
54
55
  // Access props from the component instance
@@ -131,9 +132,10 @@ const {
131
132
 
132
133
  const placeholder = computed<string | undefined>(() => {
133
134
  if (loading.value) return "Loading…";
134
- return (
135
- (control.value.uischema as { options?: { placeholder?: string } })?.options
136
- ?.placeholder ?? control.value.description
135
+ return resolvePlaceholder(
136
+ control.value.uischema,
137
+ projectedLabel.value,
138
+ "select",
137
139
  );
138
140
  });
139
141
 
@@ -47,6 +47,7 @@ import { useDerive } from "../composables/useDerive";
47
47
  import { useProjection } from "../composables/useProjection";
48
48
  import { useDirtyValidation } from "../composables/useDirtyValidation";
49
49
  import { shouldAutoSelectMulti } from "../utils/autoSelect";
50
+ import { resolvePlaceholder } from "../utils/placeholder";
50
51
  import MultiSelect from "primevue/multiselect";
51
52
 
52
53
  // Access props from the component instance
@@ -164,9 +165,10 @@ watch(
164
165
 
165
166
  const placeholder = computed<string | undefined>(() => {
166
167
  if (loading.value) return "Loading…";
167
- return (
168
- (control.value.uischema as { options?: { placeholder?: string } })?.options
169
- ?.placeholder ?? control.value.description
168
+ return resolvePlaceholder(
169
+ control.value.uischema,
170
+ projectedLabel.value,
171
+ "select",
170
172
  );
171
173
  });
172
174
 
@@ -45,6 +45,7 @@ import { useDerive } from "../composables/useDerive";
45
45
  import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
46
46
  import { useProjection } from "../composables/useProjection";
47
47
  import { useDirtyValidation } from "../composables/useDirtyValidation";
48
+ import { resolvePlaceholder } from "../utils/placeholder";
48
49
  import InputNumber from "primevue/inputnumber";
49
50
 
50
51
  // Access props from the component instance
@@ -64,8 +65,8 @@ const options = computed(
64
65
  ?.options ?? {},
65
66
  );
66
67
 
67
- const placeholder = computed<string | undefined>(
68
- () => (options.value.placeholder as string) ?? control.value.description,
68
+ const placeholder = computed<string | undefined>(() =>
69
+ resolvePlaceholder(control.value.uischema, projectedLabel.value, "input"),
69
70
  );
70
71
 
71
72
  // Add derive functionality
@@ -46,6 +46,7 @@ import { useDerive } from "../composables/useDerive";
46
46
  import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
47
47
  import { useProjection } from "../composables/useProjection";
48
48
  import { useDirtyValidation } from "../composables/useDirtyValidation";
49
+ import { resolvePlaceholder } from "../utils/placeholder";
49
50
  import InputText from "primevue/inputtext";
50
51
  import AutoComplete from "primevue/autocomplete";
51
52
 
@@ -111,16 +112,23 @@ watch(query, () => {
111
112
  if (binding.value?.load === "query") reload();
112
113
  });
113
114
 
115
+ const isAutocomplete = computed(() => !!binding.value);
116
+
114
117
  const placeholder = computed<string | undefined>(() => {
115
118
  if (loading.value) return "Loading…";
116
- return (
117
- (control.value.uischema as { options?: { placeholder?: string } })?.options
118
- ?.placeholder ?? control.value.description
119
+ const explicit = (
120
+ control.value.uischema as { options?: { placeholder?: string } }
121
+ )?.options?.placeholder;
122
+ if (explicit) return explicit;
123
+ // In autocomplete mode the input is really a search box — use matching UX.
124
+ if (isAutocomplete.value) return "Type to search…";
125
+ return resolvePlaceholder(
126
+ control.value.uischema,
127
+ projectedLabel.value,
128
+ "input",
119
129
  );
120
130
  });
121
131
 
122
- const isAutocomplete = computed(() => !!binding.value);
123
-
124
132
  // Add derive functionality
125
133
  useDerive({ control, handleChange, data: projectedData });
126
134
 
@@ -43,6 +43,7 @@ import { useJsonFormsControl } from "@jsonforms/vue";
43
43
  import { computed, getCurrentInstance } from "vue";
44
44
  import { useProjection } from "../composables/useProjection";
45
45
  import { useDirtyValidation } from "../composables/useDirtyValidation";
46
+ import { resolvePlaceholder } from "../utils/placeholder";
46
47
  import Textarea from "primevue/textarea";
47
48
 
48
49
  // Access props from the component instance
@@ -56,10 +57,8 @@ const {
56
57
  projectedLabel,
57
58
  } = useProjection(control, rawHandleChange);
58
59
 
59
- const placeholder = computed<string | undefined>(
60
- () =>
61
- (control.value.uischema as { options?: { placeholder?: string } })?.options
62
- ?.placeholder ?? control.value.description,
60
+ const placeholder = computed<string | undefined>(() =>
61
+ resolvePlaceholder(control.value.uischema, projectedLabel.value, "input"),
63
62
  );
64
63
 
65
64
  // Track user interaction — errors only show after blur
@@ -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
+ }