@narrative.io/jsonforms-provider-protocols 3.0.0-beta.22 → 3.0.0-beta.24

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.
@@ -1 +1 @@
1
- {"version":3,"file":"seedProjectionTargets.d.ts","sourceRoot":"","sources":["../../src/core/seedProjectionTargets.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,SAAS,GAClD,OAAO,CA8BT;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
1
+ {"version":3,"file":"seedProjectionTargets.d.ts","sourceRoot":"","sources":["../../src/core/seedProjectionTargets.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,SAAS,GAClD,OAAO,CAoCT;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
@@ -9,6 +9,8 @@ function seedProjectionTargets(data, uischema) {
9
9
  const segments = parseProjectionPath(projection);
10
10
  for (let i = 0; i < segments.length; i++) {
11
11
  if (typeof segments[i] !== "number") continue;
12
+ if (i + 1 >= segments.length) continue;
13
+ if (typeof segments[i + 1] !== "string") continue;
12
14
  const partial = segments.slice(0, i + 1).map((s) => String(s)).join(".");
13
15
  const fullPath = dataPath ? `${dataPath}.${partial}` : partial;
14
16
  if (getProjectedValue(result, fullPath) === void 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"seedProjectionTargets.js","sources":["../../src/core/seedProjectionTargets.ts"],"sourcesContent":["import {\n getProjectedValue,\n parseProjectionPath,\n setProjectedValue,\n} from \"./projection\";\n\n/**\n * Materialize array indices targeted by `options.projection` controls in a\n * UI schema, so that JSON Schema validators (AJV / cfworker) emit\n * `items.required` errors on otherwise-empty arrays.\n *\n * Why this exists:\n * `initFormDataFromSchema` omits optional fields, so an optional array\n * like `data_rates: { type: 'array', items: { $ref: '#/$defs/DataRate' } }`\n * produces `undefined` (or, historically, `[]`). Both shapes pass schema\n * validation: `undefined` doesn't trigger `type: array`, and `[]` has no\n * items to apply `items.required` to. As a result, a projection-targeted\n * field rendered with `Video CPM rate *` lies — the asterisk says\n * \"required\" but the validator never enforces it on an untouched form.\n *\n * This utility opts the consumer into per-item enforcement by walking the\n * UI schema, finding every `Control` with `options.projection` that\n * addresses an array index, and ensuring the corresponding data path has\n * that index materialized as at least `{}`. With the empty object in\n * place, the schema's `items.required` fires and the projected control's\n * error string surfaces at form-validity time.\n *\n * Tradeoff:\n * This re-introduces \"noise\" on untouched forms — required-field errors\n * for fields the user hasn't seen yet. Use it when validation enforcement\n * on projection targets matters more than a clean initial form state.\n * The alternative is to declare these arrays as `required` + `minItems: 1`\n * + `default: [{}]` at the schema level, which avoids needing this helper.\n *\n * Properties:\n * - Idempotent: running twice yields the same result as running once.\n * - Non-destructive: existing values at target paths are preserved.\n * - Pure: returns a new object; does not mutate `data`.\n *\n * Example:\n * ```ts\n * const data = initFormDataFromSchema(schema);\n * const seeded = seedProjectionTargets(data, uischema);\n * // For uischema controls like\n * // { type: 'Control', scope: '#/properties/data_rates',\n * // options: { projection: '0.video_rate_usd' } }\n * // seeded.data_rates is now `[{}]` (was `undefined` or `[]`).\n * ```\n */\nexport function seedProjectionTargets(\n data: unknown,\n uischema: UISchemaLike | UISchemaLike[] | undefined,\n): unknown {\n if (!uischema) return data;\n\n const controls: { scope: string; projection: string }[] = [];\n collectProjectionControls(uischema, controls);\n\n let result = data;\n for (const { scope, projection } of controls) {\n const dataPath = scopeToDataPath(scope);\n const segments = parseProjectionPath(projection);\n\n // Seed each numeric prefix in the projection path. For\n // `projection: '0.bars.0.name'`, that means seeding both\n // `<dataPath>.0` and `<dataPath>.0.bars.0`.\n for (let i = 0; i < segments.length; i++) {\n if (typeof segments[i] !== \"number\") continue;\n\n const partial = segments\n .slice(0, i + 1)\n .map((s) => String(s))\n .join(\".\");\n const fullPath = dataPath ? `${dataPath}.${partial}` : partial;\n\n if (getProjectedValue(result, fullPath) === undefined) {\n result = setProjectedValue(result, fullPath, {});\n }\n }\n }\n\n return result;\n}\n\n/**\n * Minimal UI schema shape this utility traverses. Compatible with\n * `@jsonforms/core`'s `UISchemaElement` but kept local to avoid a runtime\n * dep on `@jsonforms/core` (it's a peer dep — types only).\n */\nexport interface UISchemaLike {\n type?: string;\n scope?: string;\n options?: { projection?: string; [key: string]: unknown };\n elements?: UISchemaLike[];\n [key: string]: unknown;\n}\n\nfunction collectProjectionControls(\n ui: UISchemaLike | UISchemaLike[] | undefined,\n out: { scope: string; projection: string }[],\n): void {\n if (!ui) return;\n if (Array.isArray(ui)) {\n for (const el of ui) collectProjectionControls(el, out);\n return;\n }\n if (typeof ui !== \"object\") return;\n\n const projection = ui.options?.projection;\n if (\n ui.type === \"Control\" &&\n typeof ui.scope === \"string\" &&\n typeof projection === \"string\"\n ) {\n out.push({ scope: ui.scope, projection });\n }\n\n if (Array.isArray(ui.elements)) {\n for (const el of ui.elements) collectProjectionControls(el, out);\n }\n}\n\n/**\n * Convert a JsonForms scope pointer (`#/properties/foo/properties/bar`) to\n * a dot-separated data path (`foo.bar`). Drops `items` segments — array\n * indices are added at runtime via the projection path, not the scope.\n */\nfunction scopeToDataPath(scope: string): string {\n if (!scope.startsWith(\"#/\")) return \"\";\n const parts = scope.slice(2).split(\"/\");\n const out: string[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (parts[i] === \"properties\" && i + 1 < parts.length) {\n out.push(parts[++i]!);\n }\n }\n return out.join(\".\");\n}\n"],"names":[],"mappings":";AAiDO,SAAS,sBACd,MACA,UACS;AACT,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,WAAoD,CAAA;AAC1D,4BAA0B,UAAU,QAAQ;AAE5C,MAAI,SAAS;AACb,aAAW,EAAE,OAAO,WAAA,KAAgB,UAAU;AAC5C,UAAM,WAAW,gBAAgB,KAAK;AACtC,UAAM,WAAW,oBAAoB,UAAU;AAK/C,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAI,OAAO,SAAS,CAAC,MAAM,SAAU;AAErC,YAAM,UAAU,SACb,MAAM,GAAG,IAAI,CAAC,EACd,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,EACpB,KAAK,GAAG;AACX,YAAM,WAAW,WAAW,GAAG,QAAQ,IAAI,OAAO,KAAK;AAEvD,UAAI,kBAAkB,QAAQ,QAAQ,MAAM,QAAW;AACrD,iBAAS,kBAAkB,QAAQ,UAAU,CAAA,CAAE;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAeA,SAAS,0BACP,IACA,KACM;AACN,MAAI,CAAC,GAAI;AACT,MAAI,MAAM,QAAQ,EAAE,GAAG;AACrB,eAAW,MAAM,GAAI,2BAA0B,IAAI,GAAG;AACtD;AAAA,EACF;AACA,MAAI,OAAO,OAAO,SAAU;AAE5B,QAAM,aAAa,GAAG,SAAS;AAC/B,MACE,GAAG,SAAS,aACZ,OAAO,GAAG,UAAU,YACpB,OAAO,eAAe,UACtB;AACA,QAAI,KAAK,EAAE,OAAO,GAAG,OAAO,YAAY;AAAA,EAC1C;AAEA,MAAI,MAAM,QAAQ,GAAG,QAAQ,GAAG;AAC9B,eAAW,MAAM,GAAG,SAAU,2BAA0B,IAAI,GAAG;AAAA,EACjE;AACF;AAOA,SAAS,gBAAgB,OAAuB;AAC9C,MAAI,CAAC,MAAM,WAAW,IAAI,EAAG,QAAO;AACpC,QAAM,QAAQ,MAAM,MAAM,CAAC,EAAE,MAAM,GAAG;AACtC,QAAM,MAAgB,CAAA;AACtB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,MAAM,CAAC,MAAM,gBAAgB,IAAI,IAAI,MAAM,QAAQ;AACrD,UAAI,KAAK,MAAM,EAAE,CAAC,CAAE;AAAA,IACtB;AAAA,EACF;AACA,SAAO,IAAI,KAAK,GAAG;AACrB;"}
1
+ {"version":3,"file":"seedProjectionTargets.js","sources":["../../src/core/seedProjectionTargets.ts"],"sourcesContent":["import {\n getProjectedValue,\n parseProjectionPath,\n setProjectedValue,\n} from \"./projection\";\n\n/**\n * Materialize array indices targeted by `options.projection` controls in a\n * UI schema, so that JSON Schema validators (AJV / cfworker) emit\n * `items.required` errors on otherwise-empty arrays.\n *\n * Why this exists:\n * `initFormDataFromSchema` omits optional fields, so an optional array\n * like `data_rates: { type: 'array', items: { $ref: '#/$defs/DataRate' } }`\n * produces `undefined` (or, historically, `[]`). Both shapes pass schema\n * validation: `undefined` doesn't trigger `type: array`, and `[]` has no\n * items to apply `items.required` to. As a result, a projection-targeted\n * field rendered with `Video CPM rate *` lies — the asterisk says\n * \"required\" but the validator never enforces it on an untouched form.\n *\n * This utility opts the consumer into per-item enforcement by walking the\n * UI schema, finding every `Control` with `options.projection` that\n * addresses an array index, and ensuring the corresponding data path has\n * that index materialized as at least `{}`. With the empty object in\n * place, the schema's `items.required` fires and the projected control's\n * error string surfaces at form-validity time.\n *\n * Tradeoff:\n * This re-introduces \"noise\" on untouched forms — required-field errors\n * for fields the user hasn't seen yet. Use it when validation enforcement\n * on projection targets matters more than a clean initial form state.\n * The alternative is to declare these arrays as `required` + `minItems: 1`\n * + `default: [{}]` at the schema level, which avoids needing this helper.\n *\n * Properties:\n * - Idempotent: running twice yields the same result as running once.\n * - Non-destructive: existing values at target paths are preserved.\n * - Pure: returns a new object; does not mutate `data`.\n *\n * Example:\n * ```ts\n * const data = initFormDataFromSchema(schema);\n * const seeded = seedProjectionTargets(data, uischema);\n * // For uischema controls like\n * // { type: 'Control', scope: '#/properties/data_rates',\n * // options: { projection: '0.video_rate_usd' } }\n * // seeded.data_rates is now `[{}]` (was `undefined` or `[]`).\n * ```\n */\nexport function seedProjectionTargets(\n data: unknown,\n uischema: UISchemaLike | UISchemaLike[] | undefined,\n): unknown {\n if (!uischema) return data;\n\n const controls: { scope: string; projection: string }[] = [];\n collectProjectionControls(uischema, controls);\n\n let result = data;\n for (const { scope, projection } of controls) {\n const dataPath = scopeToDataPath(scope);\n const segments = parseProjectionPath(projection);\n\n // Seed only when a numeric segment is followed by a *string* segment —\n // i.e., the consumer is reading a property of the array item, so the\n // item must be an object. Numeric-at-end (`'0'`) addresses the array\n // element itself, which can be any type (primitive, nested array,\n // object) — we can't infer it from the uischema alone, so leave it\n // alone. Numeric-followed-by-numeric (`'0.0'`) is a nested array; we'd\n // need to seed `[]`, but again the inner item type is unknown.\n for (let i = 0; i < segments.length; i++) {\n if (typeof segments[i] !== \"number\") continue;\n if (i + 1 >= segments.length) continue;\n if (typeof segments[i + 1] !== \"string\") continue;\n\n const partial = segments\n .slice(0, i + 1)\n .map((s) => String(s))\n .join(\".\");\n const fullPath = dataPath ? `${dataPath}.${partial}` : partial;\n\n if (getProjectedValue(result, fullPath) === undefined) {\n result = setProjectedValue(result, fullPath, {});\n }\n }\n }\n\n return result;\n}\n\n/**\n * Minimal UI schema shape this utility traverses. Compatible with\n * `@jsonforms/core`'s `UISchemaElement` but kept local to avoid a runtime\n * dep on `@jsonforms/core` (it's a peer dep — types only).\n */\nexport interface UISchemaLike {\n type?: string;\n scope?: string;\n options?: { projection?: string; [key: string]: unknown };\n elements?: UISchemaLike[];\n [key: string]: unknown;\n}\n\nfunction collectProjectionControls(\n ui: UISchemaLike | UISchemaLike[] | undefined,\n out: { scope: string; projection: string }[],\n): void {\n if (!ui) return;\n if (Array.isArray(ui)) {\n for (const el of ui) collectProjectionControls(el, out);\n return;\n }\n if (typeof ui !== \"object\") return;\n\n const projection = ui.options?.projection;\n if (\n ui.type === \"Control\" &&\n typeof ui.scope === \"string\" &&\n typeof projection === \"string\"\n ) {\n out.push({ scope: ui.scope, projection });\n }\n\n if (Array.isArray(ui.elements)) {\n for (const el of ui.elements) collectProjectionControls(el, out);\n }\n}\n\n/**\n * Convert a JsonForms scope pointer (`#/properties/foo/properties/bar`) to\n * a dot-separated data path (`foo.bar`). Drops `items` segments — array\n * indices are added at runtime via the projection path, not the scope.\n */\nfunction scopeToDataPath(scope: string): string {\n if (!scope.startsWith(\"#/\")) return \"\";\n const parts = scope.slice(2).split(\"/\");\n const out: string[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (parts[i] === \"properties\" && i + 1 < parts.length) {\n out.push(parts[++i]!);\n }\n }\n return out.join(\".\");\n}\n"],"names":[],"mappings":";AAiDO,SAAS,sBACd,MACA,UACS;AACT,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,WAAoD,CAAA;AAC1D,4BAA0B,UAAU,QAAQ;AAE5C,MAAI,SAAS;AACb,aAAW,EAAE,OAAO,WAAA,KAAgB,UAAU;AAC5C,UAAM,WAAW,gBAAgB,KAAK;AACtC,UAAM,WAAW,oBAAoB,UAAU;AAS/C,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAI,OAAO,SAAS,CAAC,MAAM,SAAU;AACrC,UAAI,IAAI,KAAK,SAAS,OAAQ;AAC9B,UAAI,OAAO,SAAS,IAAI,CAAC,MAAM,SAAU;AAEzC,YAAM,UAAU,SACb,MAAM,GAAG,IAAI,CAAC,EACd,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,EACpB,KAAK,GAAG;AACX,YAAM,WAAW,WAAW,GAAG,QAAQ,IAAI,OAAO,KAAK;AAEvD,UAAI,kBAAkB,QAAQ,QAAQ,MAAM,QAAW;AACrD,iBAAS,kBAAkB,QAAQ,UAAU,CAAA,CAAE;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAeA,SAAS,0BACP,IACA,KACM;AACN,MAAI,CAAC,GAAI;AACT,MAAI,MAAM,QAAQ,EAAE,GAAG;AACrB,eAAW,MAAM,GAAI,2BAA0B,IAAI,GAAG;AACtD;AAAA,EACF;AACA,MAAI,OAAO,OAAO,SAAU;AAE5B,QAAM,aAAa,GAAG,SAAS;AAC/B,MACE,GAAG,SAAS,aACZ,OAAO,GAAG,UAAU,YACpB,OAAO,eAAe,UACtB;AACA,QAAI,KAAK,EAAE,OAAO,GAAG,OAAO,YAAY;AAAA,EAC1C;AAEA,MAAI,MAAM,QAAQ,GAAG,QAAQ,GAAG;AAC9B,eAAW,MAAM,GAAG,SAAU,2BAA0B,IAAI,GAAG;AAAA,EACjE;AACF;AAOA,SAAS,gBAAgB,OAAuB;AAC9C,MAAI,CAAC,MAAM,WAAW,IAAI,EAAG,QAAO;AACpC,QAAM,QAAQ,MAAM,MAAM,CAAC,EAAE,MAAM,GAAG;AACtC,QAAM,MAAgB,CAAA;AACtB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,MAAM,CAAC,MAAM,gBAAgB,IAAI,IAAI,MAAM,QAAQ;AACrD,UAAI,KAAK,MAAM,EAAE,CAAC,CAAE;AAAA,IACtB;AAAA,EACF;AACA,SAAO,IAAI,KAAK,GAAG;AACrB;"}
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export type { UISchemaLike } from "./core/seedProjectionTargets";
14
14
  export { RestApiProtocol } from "./protocols/rest_api";
15
15
  export { createNoEvalAjv, transformUnit, transformErrors, } from "./no-eval-ajv";
16
16
  export type { NoEvalAjv, NoEvalErrorObject, NoEvalValidateFunction, CreateNoEvalAjvOptions, } from "./no-eval-ajv";
17
- export { providerRenderers, primevueRenderers, ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, useProvider, createDataLayer, useDataLayer, JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean, } from "./vue";
17
+ export { providerRenderers, primevueRenderers, ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, ProviderObjectMultiSelect, useProvider, createDataLayer, useDataLayer, JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean, } from "./vue";
18
18
  export type { DataLayer } from "./vue";
19
19
  export interface ProviderConfig {
20
20
  protocols?: Protocol[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,EACzB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import { default as default8 } from "./vue/primevue/JfBoolean.vue.js";
20
20
  import { primevueRenderers } from "./vue/primevue/index.js";
21
21
  import { default as default9 } from "./vue/components/ProviderSelect.vue.js";
22
22
  import { default as default10 } from "./vue/components/ProviderMultiSelect.vue.js";
23
+ import { default as default11 } from "./vue/components/ProviderObjectMultiSelect.vue.js";
23
24
  import { useProvider } from "./vue/composables/useProvider.js";
24
25
  import { createDataLayer, useDataLayer } from "./vue/composables/useDataLayer.js";
25
26
  const index = {
@@ -44,6 +45,7 @@ export {
44
45
  default4 as JfTextArea,
45
46
  default2 as ProviderAutocomplete,
46
47
  default10 as ProviderMultiSelect,
48
+ default11 as ProviderObjectMultiSelect,
47
49
  default9 as ProviderSelect,
48
50
  RestApiProtocol,
49
51
  applyTransformPipeline,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\nexport * from \"./core/transforms\";\nexport * from \"./core/projection\";\nexport * from \"./core/resolveScope\";\n// Core exports\nexport * from \"./core/types\";\nexport { initFormDataFromSchema } from \"./core/initFormData\";\nexport { seedProjectionTargets } from \"./core/seedProjectionTargets\";\nexport type { UISchemaLike } from \"./core/seedProjectionTargets\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// CSP-safe validator\nexport {\n createNoEvalAjv,\n transformUnit,\n transformErrors,\n} from \"./no-eval-ajv\";\nexport type {\n NoEvalAjv,\n NoEvalErrorObject,\n NoEvalValidateFunction,\n CreateNoEvalAjvOptions,\n} from \"./no-eval-ajv\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n useProvider,\n createDataLayer,\n useDataLayer,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\nexport type { DataLayer } from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0DA,MAAA,QAAe;AAAA,EACb,QAAQ,KAAU,MAAuB;AACvC,UAAM,MAAMA;AACZ,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,KAAK,WAAW;AAC9B,YAAI,SAAS,CAAC;AAAA,MAChB;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB,GAAG;AACnC,QAAI,QAAQ,iBAAiBC,KAAW;AACxC,QAAI,QAAQ,gBAAgB,MAAM,QAAQ,CAAA,CAAE;AAAA,EAC9C;AACF;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\nexport * from \"./core/transforms\";\nexport * from \"./core/projection\";\nexport * from \"./core/resolveScope\";\n// Core exports\nexport * from \"./core/types\";\nexport { initFormDataFromSchema } from \"./core/initFormData\";\nexport { seedProjectionTargets } from \"./core/seedProjectionTargets\";\nexport type { UISchemaLike } from \"./core/seedProjectionTargets\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// CSP-safe validator\nexport {\n createNoEvalAjv,\n transformUnit,\n transformErrors,\n} from \"./no-eval-ajv\";\nexport type {\n NoEvalAjv,\n NoEvalErrorObject,\n NoEvalValidateFunction,\n CreateNoEvalAjvOptions,\n} from \"./no-eval-ajv\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n ProviderObjectMultiSelect,\n useProvider,\n createDataLayer,\n useDataLayer,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\nexport type { DataLayer } from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,MAAA,QAAe;AAAA,EACb,QAAQ,KAAU,MAAuB;AACvC,UAAM,MAAMA;AACZ,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,KAAK,WAAW;AAC9B,YAAI,SAAS,CAAC;AAAA,MAChB;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB,GAAG;AACnC,QAAI,QAAQ,iBAAiBC,KAAW;AACxC,QAAI,QAAQ,gBAAgB,MAAM,QAAQ,CAAA,CAAE;AAAA,EAC9C;AACF;"}
@@ -6,3 +6,7 @@
6
6
  [data-v-0d0d0f03] .p-multiselect-label {
7
7
  text-align: left;
8
8
  }
9
+
10
+ [data-v-2ee6c7be] .p-multiselect-label {
11
+ text-align: left;
12
+ }
@@ -0,0 +1,9 @@
1
+ import type { ControlElement, JsonSchema } from "@jsonforms/core";
2
+ type __VLS_Props = {
3
+ uischema: ControlElement;
4
+ schema: JsonSchema;
5
+ path: string;
6
+ };
7
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ export default _default;
9
+ //# sourceMappingURL=ProviderObjectMultiSelect.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProviderObjectMultiSelect.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/components/ProviderObjectMultiSelect.vue"],"names":[],"mappings":"AA2KA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAiBlE,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;;AAgOF,wBAEG"}
@@ -0,0 +1,8 @@
1
+ import _sfc_main from "./ProviderObjectMultiSelect.vue2.js";
2
+ /* empty css */
3
+ import _export_sfc from "../../_virtual/_plugin-vue_export-helper.js";
4
+ const ProviderObjectMultiSelect = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-2ee6c7be"]]);
5
+ export {
6
+ ProviderObjectMultiSelect as default
7
+ };
8
+ //# sourceMappingURL=ProviderObjectMultiSelect.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProviderObjectMultiSelect.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -0,0 +1,142 @@
1
+ import { defineComponent, inject, computed, watch, createElementBlock, openBlock, createCommentVNode, createVNode, unref, toDisplayString } from "vue";
2
+ import { useJsonFormsControl } from "@jsonforms/vue";
3
+ import { useProvider } from "../composables/useProvider.js";
4
+ import { useProjection } from "../composables/useProjection.js";
5
+ import { resolvePlaceholder } from "../utils/placeholder.js";
6
+ import { resolveItemsSchema, resolveObjectKeys, fromMultiSelectShape, sameObjectSet, toMultiSelectShape } from "../utils/objectMultiSelect.js";
7
+ import MultiSelect from "primevue/multiselect";
8
+ const _hoisted_1 = { class: "jf-control" };
9
+ const _hoisted_2 = {
10
+ key: 0,
11
+ class: "jf-label"
12
+ };
13
+ const _hoisted_3 = {
14
+ key: 1,
15
+ class: "jf-description"
16
+ };
17
+ const _hoisted_4 = {
18
+ key: 2,
19
+ class: "p-error",
20
+ role: "alert"
21
+ };
22
+ const _sfc_main = /* @__PURE__ */ defineComponent({
23
+ __name: "ProviderObjectMultiSelect",
24
+ props: {
25
+ uischema: {},
26
+ schema: {},
27
+ path: {}
28
+ },
29
+ setup(__props) {
30
+ const props = __props;
31
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
32
+ const {
33
+ projectedData,
34
+ projectedLabel,
35
+ handleProjectedChange: handleChange
36
+ } = useProjection(control, rawHandleChange);
37
+ const jsonforms = inject("jsonforms", null);
38
+ const rootSchema = computed(
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ () => jsonforms?.core?.schema ?? control.value.schema
41
+ );
42
+ const itemsSchema = computed(
43
+ () => resolveItemsSchema(
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ control.value.schema,
46
+ rootSchema.value
47
+ )
48
+ );
49
+ const objectKeys = computed(() => {
50
+ const resolved = resolveObjectKeys(
51
+ control.value.uischema?.options,
52
+ itemsSchema.value
53
+ );
54
+ if (!resolved) {
55
+ throw new Error(
56
+ "[ProviderObjectMultiSelect] objectKeys could not be resolved. Specify `uischema.options.objectKeys = { value, label }` or declare exactly two `required` properties on the array's items schema so they can be inferred."
57
+ );
58
+ }
59
+ return resolved;
60
+ });
61
+ const binding = computed(() => {
62
+ const provider = control.value.uischema?.options?.provider;
63
+ if (provider && typeof provider === "object" && !provider.load) {
64
+ return { ...provider, load: "mount" };
65
+ }
66
+ return provider;
67
+ });
68
+ const deps = computed(
69
+ () => control.value.schema?.["x-provider"]?.dependsOn ?? []
70
+ );
71
+ const depValues = computed(() => deps.value.map(() => null));
72
+ const injectedFormData = inject("formData", { value: {} });
73
+ const rootData = computed(() => injectedFormData.value || {});
74
+ const { items, loading, error } = useProvider(binding, {
75
+ data: rootData,
76
+ path: control.value.path,
77
+ dependsOnValues: depValues
78
+ });
79
+ watch(
80
+ [items, loading],
81
+ ([newItems, isLoading]) => {
82
+ if (!control.value.uischema?.options?.autoSelectSingle || isLoading || newItems.length !== 1) {
83
+ return;
84
+ }
85
+ const single = newItems[0];
86
+ const current = Array.isArray(projectedData.value) ? projectedData.value : [];
87
+ if (current.length === 0) {
88
+ handleChange(control.value.path, [
89
+ {
90
+ [objectKeys.value.value]: single.value,
91
+ [objectKeys.value.label]: single.label
92
+ }
93
+ ]);
94
+ }
95
+ },
96
+ { immediate: true }
97
+ );
98
+ const value = computed({
99
+ get() {
100
+ return toMultiSelectShape(projectedData.value, objectKeys.value);
101
+ },
102
+ set(val) {
103
+ const next = fromMultiSelectShape(val, objectKeys.value);
104
+ const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
105
+ if (!sameObjectSet(curr, next, objectKeys.value.value)) {
106
+ handleChange(control.value.path, next);
107
+ }
108
+ }
109
+ });
110
+ const placeholder = computed(() => {
111
+ if (loading.value) return "Loading…";
112
+ return resolvePlaceholder(
113
+ control.value.uischema,
114
+ projectedLabel.value,
115
+ "select"
116
+ );
117
+ });
118
+ return (_ctx, _cache) => {
119
+ return openBlock(), createElementBlock("div", _hoisted_1, [
120
+ unref(projectedLabel) ? (openBlock(), createElementBlock("label", _hoisted_2, toDisplayString(unref(projectedLabel)), 1)) : createCommentVNode("", true),
121
+ unref(control).description ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(unref(control).description), 1)) : createCommentVNode("", true),
122
+ createVNode(unref(MultiSelect), {
123
+ modelValue: value.value,
124
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => value.value = $event),
125
+ class: "w-full!",
126
+ options: unref(items),
127
+ "option-label": "label",
128
+ "data-key": "value",
129
+ display: "chip",
130
+ placeholder: placeholder.value,
131
+ disabled: !unref(control).enabled || unref(loading),
132
+ "show-clear": true
133
+ }, null, 8, ["modelValue", "options", "placeholder", "disabled"]),
134
+ unref(error) ? (openBlock(), createElementBlock("small", _hoisted_4, "Failed to load: " + toDisplayString(unref(error)), 1)) : createCommentVNode("", true)
135
+ ]);
136
+ };
137
+ }
138
+ });
139
+ export {
140
+ _sfc_main as default
141
+ };
142
+ //# sourceMappingURL=ProviderObjectMultiSelect.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProviderObjectMultiSelect.vue2.js","sources":["../../../src/vue/components/ProviderObjectMultiSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ControlElement, JsonSchema } from \"@jsonforms/core\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, inject, watch } from \"vue\";\nimport { useProvider } from \"../composables/useProvider\";\nimport { useProjection } from \"../composables/useProjection\";\nimport { resolvePlaceholder } from \"../utils/placeholder\";\nimport {\n fromMultiSelectShape,\n resolveItemsSchema,\n resolveObjectKeys,\n sameObjectSet,\n toMultiSelectShape,\n type MultiSelectOption,\n type ObjectKeys,\n} from \"../utils/objectMultiSelect\";\nimport MultiSelect from \"primevue/multiselect\";\n\nconst props = defineProps<{\n uischema: ControlElement;\n schema: JsonSchema;\n path: string;\n}>();\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst {\n projectedData,\n projectedLabel,\n handleProjectedChange: handleChange,\n} = useProjection(control, rawHandleChange);\n\n// Pull root schema so item-schema $refs resolve against the consumer's $defs.\nconst jsonforms = inject<{\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n core?: { schema?: Record<string, any> };\n} | null>(\"jsonforms\", null);\nconst rootSchema = computed(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n () => (jsonforms?.core?.schema ?? control.value.schema) as Record<string, any>,\n);\n\nconst itemsSchema = computed(() =>\n resolveItemsSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n control.value.schema as Record<string, any>,\n rootSchema.value,\n ),\n);\n\nconst objectKeys = computed<ObjectKeys>(() => {\n const resolved = resolveObjectKeys(\n control.value.uischema?.options as\n | { objectKeys?: { value?: unknown; label?: unknown } }\n | undefined,\n itemsSchema.value,\n );\n if (!resolved) {\n throw new Error(\n \"[ProviderObjectMultiSelect] objectKeys could not be resolved. \" +\n \"Specify `uischema.options.objectKeys = { value, label }` or declare \" +\n \"exactly two `required` properties on the array's items schema so \" +\n \"they can be inferred.\",\n );\n }\n return resolved;\n});\n\nconst binding = computed(() => {\n const provider = control.value.uischema?.options?.provider;\n if (provider && typeof provider === \"object\" && !provider.load) {\n return { ...provider, load: \"mount\" };\n }\n return provider;\n});\n\nconst deps = computed(\n () =>\n ((\n (control.value.schema as Record<string, unknown>)?.[\n \"x-provider\"\n ] as Record<string, unknown>\n )?.dependsOn as string[]) ?? [],\n);\nconst depValues = computed(() => deps.value.map(() => null));\n\nconst injectedFormData = inject<{ value: unknown }>(\"formData\", { value: {} });\nconst rootData = computed(() => injectedFormData.value || {});\n\nconst { items, loading, error } = useProvider(binding, {\n data: rootData,\n path: control.value.path,\n dependsOnValues: depValues,\n});\n\n// Auto-select when provider returns only one item — paired-object variant.\nwatch(\n [items, loading],\n ([newItems, isLoading]) => {\n if (\n !control.value.uischema?.options?.autoSelectSingle ||\n isLoading ||\n newItems.length !== 1\n ) {\n return;\n }\n const single = newItems[0]!;\n const current = Array.isArray(projectedData.value) ? projectedData.value : [];\n if (current.length === 0) {\n handleChange(control.value.path, [\n {\n [objectKeys.value.value]: single.value,\n [objectKeys.value.label]: single.label,\n },\n ]);\n }\n },\n { immediate: true },\n);\n\nconst value = computed<MultiSelectOption[]>({\n get() {\n return toMultiSelectShape(projectedData.value, objectKeys.value);\n },\n set(val) {\n const next = fromMultiSelectShape(val, objectKeys.value);\n const curr = Array.isArray(projectedData.value) ? projectedData.value : [];\n if (!sameObjectSet(curr, next, objectKeys.value.value)) {\n handleChange(control.value.path, next);\n }\n },\n});\n\nconst placeholder = computed(() => {\n if (loading.value) return \"Loading…\";\n return resolvePlaceholder(\n control.value.uischema,\n projectedLabel.value,\n \"select\",\n );\n});\n</script>\n\n<template>\n <div class=\"jf-control\">\n <label v-if=\"projectedLabel\" class=\"jf-label\">{{ projectedLabel }}</label>\n <div v-if=\"control.description\" class=\"jf-description\">\n {{ control.description }}\n </div>\n <MultiSelect\n v-model=\"value\"\n class=\"w-full!\"\n :options=\"items\"\n option-label=\"label\"\n data-key=\"value\"\n display=\"chip\"\n :placeholder=\"placeholder\"\n :disabled=\"!control.enabled || loading\"\n :show-clear=\"true\"\n />\n <small v-if=\"error\" class=\"p-error\" role=\"alert\"\n >Failed to load: {{ error }}</small\n >\n </div>\n</template>\n\n<style scoped>\n:deep(.p-multiselect-label) {\n text-align: left;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBA,UAAM,QAAQ;AAKd,UAAM,EAAE,SAAS,cAAc,gBAAA,IAAoB,oBAAoB,KAAK;AAC5E,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,uBAAuB;AAAA,IAAA,IACrB,cAAc,SAAS,eAAe;AAG1C,UAAM,YAAY,OAGR,aAAa,IAAI;AAC3B,UAAM,aAAa;AAAA;AAAA,MAEjB,MAAO,WAAW,MAAM,UAAU,QAAQ,MAAM;AAAA,IAAA;AAGlD,UAAM,cAAc;AAAA,MAAS,MAC3B;AAAA;AAAA,QAEE,QAAQ,MAAM;AAAA,QACd,WAAW;AAAA,MAAA;AAAA,IACb;AAGF,UAAM,aAAa,SAAqB,MAAM;AAC5C,YAAM,WAAW;AAAA,QACf,QAAQ,MAAM,UAAU;AAAA,QAGxB,YAAY;AAAA,MAAA;AAEd,UAAI,CAAC,UAAU;AACb,cAAM,IAAI;AAAA,UACR;AAAA,QAAA;AAAA,MAKJ;AACA,aAAO;AAAA,IACT,CAAC;AAED,UAAM,UAAU,SAAS,MAAM;AAC7B,YAAM,WAAW,QAAQ,MAAM,UAAU,SAAS;AAClD,UAAI,YAAY,OAAO,aAAa,YAAY,CAAC,SAAS,MAAM;AAC9D,eAAO,EAAE,GAAG,UAAU,MAAM,QAAA;AAAA,MAC9B;AACA,aAAO;AAAA,IACT,CAAC;AAED,UAAM,OAAO;AAAA,MACX,MAEK,QAAQ,MAAM,SACb,YACF,GACC,aAA0B,CAAA;AAAA,IAAC;AAElC,UAAM,YAAY,SAAS,MAAM,KAAK,MAAM,IAAI,MAAM,IAAI,CAAC;AAE3D,UAAM,mBAAmB,OAA2B,YAAY,EAAE,OAAO,CAAA,GAAI;AAC7E,UAAM,WAAW,SAAS,MAAM,iBAAiB,SAAS,CAAA,CAAE;AAE5D,UAAM,EAAE,OAAO,SAAS,MAAA,IAAU,YAAY,SAAS;AAAA,MACrD,MAAM;AAAA,MACN,MAAM,QAAQ,MAAM;AAAA,MACpB,iBAAiB;AAAA,IAAA,CAClB;AAGD;AAAA,MACE,CAAC,OAAO,OAAO;AAAA,MACf,CAAC,CAAC,UAAU,SAAS,MAAM;AACzB,YACE,CAAC,QAAQ,MAAM,UAAU,SAAS,oBAClC,aACA,SAAS,WAAW,GACpB;AACA;AAAA,QACF;AACA,cAAM,SAAS,SAAS,CAAC;AACzB,cAAM,UAAU,MAAM,QAAQ,cAAc,KAAK,IAAI,cAAc,QAAQ,CAAA;AAC3E,YAAI,QAAQ,WAAW,GAAG;AACxB,uBAAa,QAAQ,MAAM,MAAM;AAAA,YAC/B;AAAA,cACE,CAAC,WAAW,MAAM,KAAK,GAAG,OAAO;AAAA,cACjC,CAAC,WAAW,MAAM,KAAK,GAAG,OAAO;AAAA,YAAA;AAAA,UACnC,CACD;AAAA,QACH;AAAA,MACF;AAAA,MACA,EAAE,WAAW,KAAA;AAAA,IAAK;AAGpB,UAAM,QAAQ,SAA8B;AAAA,MAC1C,MAAM;AACJ,eAAO,mBAAmB,cAAc,OAAO,WAAW,KAAK;AAAA,MACjE;AAAA,MACA,IAAI,KAAK;AACP,cAAM,OAAO,qBAAqB,KAAK,WAAW,KAAK;AACvD,cAAM,OAAO,MAAM,QAAQ,cAAc,KAAK,IAAI,cAAc,QAAQ,CAAA;AACxE,YAAI,CAAC,cAAc,MAAM,MAAM,WAAW,MAAM,KAAK,GAAG;AACtD,uBAAa,QAAQ,MAAM,MAAM,IAAI;AAAA,QACvC;AAAA,MACF;AAAA,IAAA,CACD;AAED,UAAM,cAAc,SAAS,MAAM;AACjC,UAAI,QAAQ,MAAO,QAAO;AAC1B,aAAO;AAAA,QACL,QAAQ,MAAM;AAAA,QACd,eAAe;AAAA,QACf;AAAA,MAAA;AAAA,IAEJ,CAAC;;AAIC,aAAAA,UAAA,GAAAC,mBAmBM,OAnBN,YAmBM;AAAA,QAlBSC,MAAA,cAAA,kBAAbD,mBAA0E,SAA1E,YAA0EE,gBAAzBD,MAAA,cAAA,CAAc,GAAA,CAAA;QACpDA,MAAA,OAAA,EAAQ,eAAnBF,UAAA,GAAAC,mBAEM,OAFN,YAEME,gBADDD,MAAA,OAAA,EAAQ,WAAW,GAAA,CAAA;QAExBE,YAUEF,MAAA,WAAA,GAAA;AAAA,sBATS,MAAA;AAAA,uEAAA,MAAK,QAAA;AAAA,UACd,OAAM;AAAA,UACL,SAASA,MAAA,KAAA;AAAA,UACV,gBAAa;AAAA,UACb,YAAS;AAAA,UACT,SAAQ;AAAA,UACP,aAAa,YAAA;AAAA,UACb,UAAQ,CAAGA,MAAA,OAAA,EAAQ,WAAWA,MAAA,OAAA;AAAA,UAC9B,cAAY;AAAA,QAAA;QAEFA,MAAA,KAAA,KAAbF,aAAAC,mBAEC,SAFD,YACG,qCAAmBC,MAAA,KAAA,CAAK,GAAA,CAAA;;;;;"}
@@ -2,6 +2,7 @@ import type { UISchemaElement } from "@jsonforms/core";
2
2
  import ProviderAutocomplete from "./components/ProviderAutocomplete.vue";
3
3
  import ProviderSelect from "./components/ProviderSelect.vue";
4
4
  import ProviderMultiSelect from "./components/ProviderMultiSelect.vue";
5
+ import ProviderObjectMultiSelect from "./components/ProviderObjectMultiSelect.vue";
5
6
  export declare const providerRenderers: {
6
7
  tester: (uischema: UISchemaElement, schema: import("@jsonforms/core").JsonSchema, context: import("@jsonforms/core").TesterContext) => number;
7
8
  renderer: import("vue").DefineComponent<{
@@ -15,7 +16,7 @@ export declare const providerRenderers: {
15
16
  }> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
17
  }[];
17
18
  export { primevueRenderers, registerPrimevueRenderers } from "./primevue";
18
- export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect };
19
+ export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, ProviderObjectMultiSelect, };
19
20
  export { useProvider } from "./composables/useProvider";
20
21
  export { useProjection } from "./composables/useProjection";
21
22
  export type { ProjectionResult } from "./composables/useProjection";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAWvD,OAAO,oBAAoB,MAAM,uCAAuC,CAAC;AACzE,OAAO,cAAc,MAAM,iCAAiC,CAAC;AAC7D,OAAO,mBAAmB,MAAM,sCAAsC,CAAC;AAqEvE,eAAO,MAAM,iBAAiB;;;;;;;;;;;GAI7B,CAAC;AAGF,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG1E,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC3E,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,cAAc,WAAW,CAAC;AAG1B,OAAO,EACL,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAYvD,OAAO,oBAAoB,MAAM,uCAAuC,CAAC;AACzE,OAAO,cAAc,MAAM,iCAAiC,CAAC;AAC7D,OAAO,mBAAmB,MAAM,sCAAsC,CAAC;AACvE,OAAO,yBAAyB,MAAM,4CAA4C,CAAC;AAoGnF,eAAO,MAAM,iBAAiB;;;;;;;;;;;GAQ7B,CAAC;AAGF,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG1E,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,GAC1B,CAAC;AACF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC3E,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,cAAc,WAAW,CAAC;AAG1B,OAAO,EACL,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC"}
package/dist/vue/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { rankWith, and, or, isStringControl, isNumberControl, isIntegerControl, isControl } from "@jsonforms/core";
2
2
  import { resolveScopeSchema } from "../core/resolveScope.js";
3
+ import { resolveItemsSchema } from "./utils/objectMultiSelect.js";
3
4
  import _sfc_main from "./components/ProviderAutocomplete.vue.js";
4
5
  import ProviderSelect from "./components/ProviderSelect.vue.js";
5
6
  import ProviderMultiSelect from "./components/ProviderMultiSelect.vue.js";
7
+ import ProviderObjectMultiSelect from "./components/ProviderObjectMultiSelect.vue.js";
6
8
  import { primevueRenderers, registerPrimevueRenderers } from "./primevue/index.js";
7
9
  import { useProvider } from "./composables/useProvider.js";
8
10
  import { useProjection } from "./composables/useProjection.js";
@@ -57,24 +59,51 @@ const providerAutocompleteTester = rankWith(
57
59
  (uischema) => uischema?.options?.autocomplete === true
58
60
  )
59
61
  );
60
- const isArrayControl = (uischema, schema) => {
62
+ const getArraySchema = (uischema, schema) => {
61
63
  const controlSchema = uischema;
62
- if (controlSchema.type !== "Control" || !controlSchema.scope) {
63
- return false;
64
- }
64
+ if (controlSchema.type !== "Control" || !controlSchema.scope) return void 0;
65
65
  const propertySchema = resolveScopeSchema(
66
66
  controlSchema.scope,
67
67
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
68
  schema
69
69
  );
70
- return propertySchema?.type === "array";
70
+ if (propertySchema?.type !== "array") return void 0;
71
+ return propertySchema;
71
72
  };
73
+ const isObjectArrayControl = (uischema, schema) => {
74
+ const arr = getArraySchema(uischema, schema);
75
+ if (!arr) return false;
76
+ const items = resolveItemsSchema(
77
+ arr,
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ schema
80
+ );
81
+ return items?.type === "object";
82
+ };
83
+ const isScalarArrayControl = (uischema, schema) => {
84
+ const arr = getArraySchema(uischema, schema);
85
+ if (!arr) return false;
86
+ const items = resolveItemsSchema(
87
+ arr,
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ schema
90
+ );
91
+ return items?.type !== "object";
92
+ };
93
+ const providerObjectMultiSelectTester = rankWith(
94
+ 110,
95
+ // One higher than scalar multi-select so object-item arrays match first.
96
+ and(isObjectArrayControl, hasProvider)
97
+ );
72
98
  const providerMultiSelectTester = rankWith(
73
99
  109,
74
- // Highest priority for array controls with providers
75
- and(isArrayControl, hasProvider)
100
+ and(isScalarArrayControl, hasProvider)
76
101
  );
77
102
  const providerRenderers = [
103
+ {
104
+ tester: providerObjectMultiSelectTester,
105
+ renderer: ProviderObjectMultiSelect
106
+ },
78
107
  { tester: providerMultiSelectTester, renderer: ProviderMultiSelect },
79
108
  { tester: providerAutocompleteTester, renderer: _sfc_main },
80
109
  { tester: providerSelectTester, renderer: ProviderSelect }
@@ -88,6 +117,7 @@ export {
88
117
  default3 as JfTextArea,
89
118
  _sfc_main as ProviderAutocomplete,
90
119
  ProviderMultiSelect,
120
+ ProviderObjectMultiSelect,
91
121
  ProviderSelect,
92
122
  createDataLayer,
93
123
  primevueRenderers,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/vue/index.ts"],"sourcesContent":["import type { UISchemaElement } from \"@jsonforms/core\";\nimport {\n and,\n isIntegerControl,\n isNumberControl,\n isStringControl,\n or,\n rankWith,\n isControl,\n} from \"@jsonforms/core\";\nimport { resolveScopeSchema } from \"../core/resolveScope\";\nimport ProviderAutocomplete from \"./components/ProviderAutocomplete.vue\";\nimport ProviderSelect from \"./components/ProviderSelect.vue\";\nimport ProviderMultiSelect from \"./components/ProviderMultiSelect.vue\";\n\n// Custom tester that checks if provider option exists (as object or boolean)\nconst hasProvider = (uischema: UISchemaElement) => {\n return uischema?.options?.provider !== undefined;\n};\n\n// Integer fallback tester — handles nested scopes like #/properties/parent/properties/child\nconst isIntegerScope = (uischema: unknown, schema: unknown) => {\n const ui = uischema as { type?: string; scope?: string };\n if (ui?.type !== \"Control\" || !ui?.scope) return false;\n\n const propertySchema = resolveScopeSchema(\n ui.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n return propertySchema?.type === \"integer\";\n};\n\n// Create specific testers for each component type\nconst providerSelectTester = rankWith(\n 107, // Higher than PrimeVue JfNumber integer renderer (106) so provider wins\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => !uischema?.options?.autocomplete,\n ),\n);\n\nconst providerAutocompleteTester = rankWith(\n 108, // Higher than providerSelectTester so autocomplete variant wins when flagged\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => uischema?.options?.autocomplete === true,\n ),\n);\n\n// Custom array tester - supports nested scope paths\nconst isArrayControl = (uischema: UISchemaElement, schema: unknown) => {\n const controlSchema = uischema as { type: string; scope?: string };\n if (controlSchema.type !== \"Control\" || !controlSchema.scope) {\n return false;\n }\n\n const propertySchema = resolveScopeSchema(\n controlSchema.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n return propertySchema?.type === \"array\";\n};\n\nconst providerMultiSelectTester = rankWith(\n 109, // Highest priority for array controls with providers\n and(isArrayControl, hasProvider),\n);\n\nexport const providerRenderers = [\n { tester: providerMultiSelectTester, renderer: ProviderMultiSelect },\n { tester: providerAutocompleteTester, renderer: ProviderAutocomplete },\n { tester: providerSelectTester, renderer: ProviderSelect },\n];\n\n// Export PrimeVue renderers (styles are auto-injected)\nexport { primevueRenderers, registerPrimevueRenderers } from \"./primevue\";\n\n// Export individual components\nexport { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect };\nexport { useProvider } from \"./composables/useProvider\";\nexport { useProjection } from \"./composables/useProjection\";\nexport type { ProjectionResult } from \"./composables/useProjection\";\nexport { createDataLayer, useDataLayer } from \"./composables/useDataLayer\";\nexport type { DataLayer } from \"./composables/useDataLayer\";\nexport { useDeriveInitialValue } from \"./composables/useDeriveInitialValue\";\nexport type { DeriveInitialValueCfg } from \"./composables/useDeriveInitialValue\";\nexport { useDirtyValidation } from \"./composables/useDirtyValidation\";\nexport * from \"./testers\";\n\n// Export individual PrimeVue components using lazy evaluation to avoid circular deps\nexport {\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./primevue\";\n"],"names":["ProviderAutocomplete"],"mappings":";;;;;;;;;;;;;;;;;;AAgBA,MAAM,cAAc,CAAC,aAA8B;AACjD,SAAO,UAAU,SAAS,aAAa;AACzC;AAGA,MAAM,iBAAiB,CAAC,UAAmB,WAAoB;AAC7D,QAAM,KAAK;AACX,MAAI,IAAI,SAAS,aAAa,CAAC,IAAI,MAAO,QAAO;AAEjD,QAAM,iBAAiB;AAAA,IACrB,GAAG;AAAA;AAAA,IAEH;AAAA,EAAA;AAEF,SAAO,gBAAgB,SAAS;AAClC;AAGA,MAAM,uBAAuB;AAAA,EAC3B;AAAA;AAAA,EACA;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,WAAW,cAAc;AAAA,IAAA;AAAA,IAE/B;AAAA,IACA,CAAC,aAAa,CAAC,UAAU,SAAS;AAAA,EAAA;AAEtC;AAEA,MAAM,6BAA6B;AAAA,EACjC;AAAA;AAAA,EACA;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,WAAW,cAAc;AAAA,IAAA;AAAA,IAE/B;AAAA,IACA,CAAC,aAAa,UAAU,SAAS,iBAAiB;AAAA,EAAA;AAEtD;AAGA,MAAM,iBAAiB,CAAC,UAA2B,WAAoB;AACrE,QAAM,gBAAgB;AACtB,MAAI,cAAc,SAAS,aAAa,CAAC,cAAc,OAAO;AAC5D,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB;AAAA,IACrB,cAAc;AAAA;AAAA,IAEd;AAAA,EAAA;AAEF,SAAO,gBAAgB,SAAS;AAClC;AAEA,MAAM,4BAA4B;AAAA,EAChC;AAAA;AAAA,EACA,IAAI,gBAAgB,WAAW;AACjC;AAEO,MAAM,oBAAoB;AAAA,EAC/B,EAAE,QAAQ,2BAA2B,UAAU,oBAAA;AAAA,EAC/C,EAAE,QAAQ,4BAA4B,UAAUA,UAAA;AAAA,EAChD,EAAE,QAAQ,sBAAsB,UAAU,eAAA;AAC5C;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/vue/index.ts"],"sourcesContent":["import type { UISchemaElement } from \"@jsonforms/core\";\nimport {\n and,\n isIntegerControl,\n isNumberControl,\n isStringControl,\n or,\n rankWith,\n isControl,\n} from \"@jsonforms/core\";\nimport { resolveScopeSchema } from \"../core/resolveScope\";\nimport { resolveItemsSchema } from \"./utils/objectMultiSelect\";\nimport ProviderAutocomplete from \"./components/ProviderAutocomplete.vue\";\nimport ProviderSelect from \"./components/ProviderSelect.vue\";\nimport ProviderMultiSelect from \"./components/ProviderMultiSelect.vue\";\nimport ProviderObjectMultiSelect from \"./components/ProviderObjectMultiSelect.vue\";\n\n// Custom tester that checks if provider option exists (as object or boolean)\nconst hasProvider = (uischema: UISchemaElement) => {\n return uischema?.options?.provider !== undefined;\n};\n\n// Integer fallback tester — handles nested scopes like #/properties/parent/properties/child\nconst isIntegerScope = (uischema: unknown, schema: unknown) => {\n const ui = uischema as { type?: string; scope?: string };\n if (ui?.type !== \"Control\" || !ui?.scope) return false;\n\n const propertySchema = resolveScopeSchema(\n ui.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n return propertySchema?.type === \"integer\";\n};\n\n// Create specific testers for each component type\nconst providerSelectTester = rankWith(\n 107, // Higher than PrimeVue JfNumber integer renderer (106) so provider wins\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => !uischema?.options?.autocomplete,\n ),\n);\n\nconst providerAutocompleteTester = rankWith(\n 108, // Higher than providerSelectTester so autocomplete variant wins when flagged\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => uischema?.options?.autocomplete === true,\n ),\n);\n\n// Resolve the array schema at a control's scope, or undefined if the\n// control isn't an array. Shared by both array testers below.\nconst getArraySchema = (uischema: UISchemaElement, schema: unknown) => {\n const controlSchema = uischema as { type: string; scope?: string };\n if (controlSchema.type !== \"Control\" || !controlSchema.scope) return undefined;\n const propertySchema = resolveScopeSchema(\n controlSchema.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n if (propertySchema?.type !== \"array\") return undefined;\n return propertySchema;\n};\n\n// Object-item arrays — render with paired-object MultiSelect.\nconst isObjectArrayControl = (uischema: UISchemaElement, schema: unknown) => {\n const arr = getArraySchema(uischema, schema);\n if (!arr) return false;\n const items = resolveItemsSchema(\n arr,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n return items?.type === \"object\";\n};\n\n// Scalar-item arrays — the original tester. Excludes object-item arrays so\n// the new ObjectMultiSelect renderer can claim them at a higher rank.\nconst isScalarArrayControl = (uischema: UISchemaElement, schema: unknown) => {\n const arr = getArraySchema(uischema, schema);\n if (!arr) return false;\n const items = resolveItemsSchema(\n arr,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n // Treat unknown / no-items shapes as scalar (matches prior behavior where\n // any array control was eligible for ProviderMultiSelect).\n return items?.type !== \"object\";\n};\n\nconst providerObjectMultiSelectTester = rankWith(\n 110, // One higher than scalar multi-select so object-item arrays match first.\n and(isObjectArrayControl, hasProvider),\n);\n\nconst providerMultiSelectTester = rankWith(\n 109,\n and(isScalarArrayControl, hasProvider),\n);\n\nexport const providerRenderers = [\n {\n tester: providerObjectMultiSelectTester,\n renderer: ProviderObjectMultiSelect,\n },\n { tester: providerMultiSelectTester, renderer: ProviderMultiSelect },\n { tester: providerAutocompleteTester, renderer: ProviderAutocomplete },\n { tester: providerSelectTester, renderer: ProviderSelect },\n];\n\n// Export PrimeVue renderers (styles are auto-injected)\nexport { primevueRenderers, registerPrimevueRenderers } from \"./primevue\";\n\n// Export individual components\nexport {\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n ProviderObjectMultiSelect,\n};\nexport { useProvider } from \"./composables/useProvider\";\nexport { useProjection } from \"./composables/useProjection\";\nexport type { ProjectionResult } from \"./composables/useProjection\";\nexport { createDataLayer, useDataLayer } from \"./composables/useDataLayer\";\nexport type { DataLayer } from \"./composables/useDataLayer\";\nexport { useDeriveInitialValue } from \"./composables/useDeriveInitialValue\";\nexport type { DeriveInitialValueCfg } from \"./composables/useDeriveInitialValue\";\nexport { useDirtyValidation } from \"./composables/useDirtyValidation\";\nexport * from \"./testers\";\n\n// Export individual PrimeVue components using lazy evaluation to avoid circular deps\nexport {\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./primevue\";\n"],"names":["ProviderAutocomplete"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkBA,MAAM,cAAc,CAAC,aAA8B;AACjD,SAAO,UAAU,SAAS,aAAa;AACzC;AAGA,MAAM,iBAAiB,CAAC,UAAmB,WAAoB;AAC7D,QAAM,KAAK;AACX,MAAI,IAAI,SAAS,aAAa,CAAC,IAAI,MAAO,QAAO;AAEjD,QAAM,iBAAiB;AAAA,IACrB,GAAG;AAAA;AAAA,IAEH;AAAA,EAAA;AAEF,SAAO,gBAAgB,SAAS;AAClC;AAGA,MAAM,uBAAuB;AAAA,EAC3B;AAAA;AAAA,EACA;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,WAAW,cAAc;AAAA,IAAA;AAAA,IAE/B;AAAA,IACA,CAAC,aAAa,CAAC,UAAU,SAAS;AAAA,EAAA;AAEtC;AAEA,MAAM,6BAA6B;AAAA,EACjC;AAAA;AAAA,EACA;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,WAAW,cAAc;AAAA,IAAA;AAAA,IAE/B;AAAA,IACA,CAAC,aAAa,UAAU,SAAS,iBAAiB;AAAA,EAAA;AAEtD;AAIA,MAAM,iBAAiB,CAAC,UAA2B,WAAoB;AACrE,QAAM,gBAAgB;AACtB,MAAI,cAAc,SAAS,aAAa,CAAC,cAAc,MAAO,QAAO;AACrE,QAAM,iBAAiB;AAAA,IACrB,cAAc;AAAA;AAAA,IAEd;AAAA,EAAA;AAEF,MAAI,gBAAgB,SAAS,QAAS,QAAO;AAC7C,SAAO;AACT;AAGA,MAAM,uBAAuB,CAAC,UAA2B,WAAoB;AAC3E,QAAM,MAAM,eAAe,UAAU,MAAM;AAC3C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ;AAAA,IACZ;AAAA;AAAA,IAEA;AAAA,EAAA;AAEF,SAAO,OAAO,SAAS;AACzB;AAIA,MAAM,uBAAuB,CAAC,UAA2B,WAAoB;AAC3E,QAAM,MAAM,eAAe,UAAU,MAAM;AAC3C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ;AAAA,IACZ;AAAA;AAAA,IAEA;AAAA,EAAA;AAIF,SAAO,OAAO,SAAS;AACzB;AAEA,MAAM,kCAAkC;AAAA,EACtC;AAAA;AAAA,EACA,IAAI,sBAAsB,WAAW;AACvC;AAEA,MAAM,4BAA4B;AAAA,EAChC;AAAA,EACA,IAAI,sBAAsB,WAAW;AACvC;AAEO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,EAAE,QAAQ,2BAA2B,UAAU,oBAAA;AAAA,EAC/C,EAAE,QAAQ,4BAA4B,UAAUA,UAAA;AAAA,EAChD,EAAE,QAAQ,sBAAsB,UAAU,eAAA;AAC5C;"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Helpers for `ProviderObjectMultiSelect`: bidirectional translation between
3
+ * the form-data shape (paired objects with consumer-named keys) and the
4
+ * MultiSelect model shape (`{ value, label }` matching the provider's `map`
5
+ * config), plus `objectKeys` inference when the consumer doesn't specify
6
+ * them on the uischema.
7
+ *
8
+ * Pure functions — no Vue, no rendering — so the renderer can compose them
9
+ * and tests can exercise the logic in isolation.
10
+ */
11
+ export interface ObjectKeys {
12
+ /** Property name on the form-data object that holds the identifier. */
13
+ value: string;
14
+ /** Property name on the form-data object that holds the display string. */
15
+ label: string;
16
+ }
17
+ export interface MultiSelectOption {
18
+ value: unknown;
19
+ label: string;
20
+ [key: string]: unknown;
21
+ }
22
+ /**
23
+ * Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the
24
+ * shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items
25
+ * already in `{ value, label }` shape pass through; missing keys yield
26
+ * `undefined` / `""` so the renderer doesn't blow up on partial data.
27
+ */
28
+ export declare function toMultiSelectShape(formData: unknown, keys: ObjectKeys): MultiSelectOption[];
29
+ /**
30
+ * Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items
31
+ * back into form-data shape using the consumer-specified property names.
32
+ */
33
+ export declare function fromMultiSelectShape(modelData: unknown, keys: ObjectKeys): Record<string, unknown>[];
34
+ /**
35
+ * Order-insensitive equality of two object-shape selections by the
36
+ * identifier property. Used to short-circuit redundant `handleChange`
37
+ * calls when the model emits the same selection under reference inequality.
38
+ */
39
+ export declare function sameObjectSet(a: unknown, b: unknown, identifierKey: string): boolean;
40
+ /**
41
+ * Resolve the items schema of an array control, dereferencing `items.$ref`
42
+ * against the root if present. Returns `undefined` if no items schema exists.
43
+ */
44
+ export declare function resolveItemsSchema(arraySchema: Record<string, any> | undefined, rootSchema: Record<string, any>): Record<string, any> | undefined;
45
+ /**
46
+ * Infer `objectKeys` from a resolved items schema when the consumer hasn't
47
+ * specified them on the uischema.
48
+ *
49
+ * Strategy: look at `items.required`. If it has exactly two entries, the
50
+ * one whose property has `format: 'uuid'` becomes `value`; the other
51
+ * becomes `label`. If neither has a uuid format, the first entry is
52
+ * `value`, the second is `label`. Returns `undefined` (and the renderer
53
+ * throws at mount) for any other shape — explicit `objectKeys` is required.
54
+ */
55
+ export declare function inferObjectKeys(itemsSchema: Record<string, any> | undefined): ObjectKeys | undefined;
56
+ /**
57
+ * Resolve the active `objectKeys` for a control: prefer the explicit
58
+ * `uischema.options.objectKeys`, fall back to schema-driven inference.
59
+ * Returns `undefined` when neither is available; the renderer surfaces a
60
+ * runtime error in that case so the consumer knows to be explicit.
61
+ */
62
+ export declare function resolveObjectKeys(uischemaOptions: {
63
+ objectKeys?: {
64
+ value?: unknown;
65
+ label?: unknown;
66
+ };
67
+ } | undefined, itemsSchema: Record<string, any> | undefined): ObjectKeys | undefined;
68
+ //# sourceMappingURL=objectMultiSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objectMultiSelect.d.ts","sourceRoot":"","sources":["../../../src/vue/utils/objectMultiSelect.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GACf,iBAAiB,EAAE,CAqBrB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,OAAO,EAClB,IAAI,EAAE,UAAU,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAW3B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,CAAC,EAAE,OAAO,EACV,CAAC,EAAE,OAAO,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAaT;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAEhC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAE5C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAE9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAOjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAE7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC3C,UAAU,GAAG,SAAS,CAcxB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,eAAe,EAAE;IAAE,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,EAElF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC3C,UAAU,GAAG,SAAS,CAUxB"}
@@ -0,0 +1,72 @@
1
+ import { deref } from "../../core/refs.js";
2
+ function toMultiSelectShape(formData, keys) {
3
+ if (!Array.isArray(formData)) return [];
4
+ return formData.filter((item) => item !== null && typeof item === "object").map((item) => {
5
+ const obj = item;
6
+ if (!(keys.value in obj) && !(keys.label in obj) && "value" in obj && "label" in obj) {
7
+ return obj;
8
+ }
9
+ return {
10
+ value: obj[keys.value],
11
+ label: typeof obj[keys.label] === "string" ? obj[keys.label] : ""
12
+ };
13
+ });
14
+ }
15
+ function fromMultiSelectShape(modelData, keys) {
16
+ if (!Array.isArray(modelData)) return [];
17
+ return modelData.filter((item) => item !== null && typeof item === "object").map((item) => {
18
+ const obj = item;
19
+ return {
20
+ [keys.value]: obj.value,
21
+ [keys.label]: obj.label
22
+ };
23
+ });
24
+ }
25
+ function sameObjectSet(a, b, identifierKey) {
26
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
27
+ return false;
28
+ }
29
+ const ids = new Set(
30
+ b.filter((x) => x !== null && typeof x === "object").map((x) => x[identifierKey])
31
+ );
32
+ return a.every((x) => {
33
+ if (x === null || typeof x !== "object") return false;
34
+ return ids.has(x[identifierKey]);
35
+ });
36
+ }
37
+ function resolveItemsSchema(arraySchema, rootSchema) {
38
+ if (!arraySchema || typeof arraySchema !== "object") return void 0;
39
+ const items = arraySchema.items;
40
+ if (!items || typeof items !== "object" || Array.isArray(items)) {
41
+ return void 0;
42
+ }
43
+ return deref(items, rootSchema);
44
+ }
45
+ function inferObjectKeys(itemsSchema) {
46
+ if (!itemsSchema || itemsSchema.type !== "object") return void 0;
47
+ const required = itemsSchema.required;
48
+ if (!Array.isArray(required) || required.length !== 2) return void 0;
49
+ const [a, b] = required;
50
+ const props = itemsSchema.properties ?? {};
51
+ const aIsUuid = props[a]?.format === "uuid";
52
+ const bIsUuid = props[b]?.format === "uuid";
53
+ if (aIsUuid && !bIsUuid) return { value: a, label: b };
54
+ if (bIsUuid && !aIsUuid) return { value: b, label: a };
55
+ return { value: a, label: b };
56
+ }
57
+ function resolveObjectKeys(uischemaOptions, itemsSchema) {
58
+ const explicit = uischemaOptions?.objectKeys;
59
+ if (explicit && typeof explicit.value === "string" && typeof explicit.label === "string") {
60
+ return { value: explicit.value, label: explicit.label };
61
+ }
62
+ return inferObjectKeys(itemsSchema);
63
+ }
64
+ export {
65
+ fromMultiSelectShape,
66
+ inferObjectKeys,
67
+ resolveItemsSchema,
68
+ resolveObjectKeys,
69
+ sameObjectSet,
70
+ toMultiSelectShape
71
+ };
72
+ //# sourceMappingURL=objectMultiSelect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objectMultiSelect.js","sources":["../../../src/vue/utils/objectMultiSelect.ts"],"sourcesContent":["import { deref } from \"../../core/refs\";\n\n/**\n * Helpers for `ProviderObjectMultiSelect`: bidirectional translation between\n * the form-data shape (paired objects with consumer-named keys) and the\n * MultiSelect model shape (`{ value, label }` matching the provider's `map`\n * config), plus `objectKeys` inference when the consumer doesn't specify\n * them on the uischema.\n *\n * Pure functions — no Vue, no rendering — so the renderer can compose them\n * and tests can exercise the logic in isolation.\n */\n\nexport interface ObjectKeys {\n /** Property name on the form-data object that holds the identifier. */\n value: string;\n /** Property name on the form-data object that holds the display string. */\n label: string;\n}\n\nexport interface MultiSelectOption {\n value: unknown;\n label: string;\n [key: string]: unknown;\n}\n\n/**\n * Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the\n * shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items\n * already in `{ value, label }` shape pass through; missing keys yield\n * `undefined` / `\"\"` so the renderer doesn't blow up on partial data.\n */\nexport function toMultiSelectShape(\n formData: unknown,\n keys: ObjectKeys,\n): MultiSelectOption[] {\n if (!Array.isArray(formData)) return [];\n return formData\n .filter((item) => item !== null && typeof item === \"object\")\n .map((item) => {\n const obj = item as Record<string, unknown>;\n // Tolerate already-translated items: if both `value` and `label` are\n // present and the form-data keys aren't, assume MultiSelect shape.\n if (\n !(keys.value in obj) &&\n !(keys.label in obj) &&\n \"value\" in obj &&\n \"label\" in obj\n ) {\n return obj as MultiSelectOption;\n }\n return {\n value: obj[keys.value],\n label: typeof obj[keys.label] === \"string\" ? (obj[keys.label] as string) : \"\",\n };\n });\n}\n\n/**\n * Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items\n * back into form-data shape using the consumer-specified property names.\n */\nexport function fromMultiSelectShape(\n modelData: unknown,\n keys: ObjectKeys,\n): Record<string, unknown>[] {\n if (!Array.isArray(modelData)) return [];\n return modelData\n .filter((item) => item !== null && typeof item === \"object\")\n .map((item) => {\n const obj = item as Record<string, unknown>;\n return {\n [keys.value]: obj.value,\n [keys.label]: obj.label,\n };\n });\n}\n\n/**\n * Order-insensitive equality of two object-shape selections by the\n * identifier property. Used to short-circuit redundant `handleChange`\n * calls when the model emits the same selection under reference inequality.\n */\nexport function sameObjectSet(\n a: unknown,\n b: unknown,\n identifierKey: string,\n): boolean {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {\n return false;\n }\n const ids = new Set(\n b\n .filter((x) => x !== null && typeof x === \"object\")\n .map((x) => (x as Record<string, unknown>)[identifierKey]),\n );\n return a.every((x) => {\n if (x === null || typeof x !== \"object\") return false;\n return ids.has((x as Record<string, unknown>)[identifierKey]);\n });\n}\n\n/**\n * Resolve the items schema of an array control, dereferencing `items.$ref`\n * against the root if present. Returns `undefined` if no items schema exists.\n */\nexport function resolveItemsSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n arraySchema: Record<string, any> | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rootSchema: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!arraySchema || typeof arraySchema !== \"object\") return undefined;\n const items = arraySchema.items;\n if (!items || typeof items !== \"object\" || Array.isArray(items)) {\n return undefined;\n }\n return deref(items, rootSchema);\n}\n\n/**\n * Infer `objectKeys` from a resolved items schema when the consumer hasn't\n * specified them on the uischema.\n *\n * Strategy: look at `items.required`. If it has exactly two entries, the\n * one whose property has `format: 'uuid'` becomes `value`; the other\n * becomes `label`. If neither has a uuid format, the first entry is\n * `value`, the second is `label`. Returns `undefined` (and the renderer\n * throws at mount) for any other shape — explicit `objectKeys` is required.\n */\nexport function inferObjectKeys(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n itemsSchema: Record<string, any> | undefined,\n): ObjectKeys | undefined {\n if (!itemsSchema || itemsSchema.type !== \"object\") return undefined;\n const required = itemsSchema.required;\n if (!Array.isArray(required) || required.length !== 2) return undefined;\n const [a, b] = required as [string, string];\n const props = (itemsSchema.properties ?? {}) as Record<\n string,\n { format?: string }\n >;\n const aIsUuid = props[a]?.format === \"uuid\";\n const bIsUuid = props[b]?.format === \"uuid\";\n if (aIsUuid && !bIsUuid) return { value: a, label: b };\n if (bIsUuid && !aIsUuid) return { value: b, label: a };\n return { value: a, label: b };\n}\n\n/**\n * Resolve the active `objectKeys` for a control: prefer the explicit\n * `uischema.options.objectKeys`, fall back to schema-driven inference.\n * Returns `undefined` when neither is available; the renderer surfaces a\n * runtime error in that case so the consumer knows to be explicit.\n */\nexport function resolveObjectKeys(\n uischemaOptions: { objectKeys?: { value?: unknown; label?: unknown } } | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n itemsSchema: Record<string, any> | undefined,\n): ObjectKeys | undefined {\n const explicit = uischemaOptions?.objectKeys;\n if (\n explicit &&\n typeof explicit.value === \"string\" &&\n typeof explicit.label === \"string\"\n ) {\n return { value: explicit.value, label: explicit.label };\n }\n return inferObjectKeys(itemsSchema);\n}\n"],"names":[],"mappings":";AAgCO,SAAS,mBACd,UACA,MACqB;AACrB,MAAI,CAAC,MAAM,QAAQ,QAAQ,UAAU,CAAA;AACrC,SAAO,SACJ,OAAO,CAAC,SAAS,SAAS,QAAQ,OAAO,SAAS,QAAQ,EAC1D,IAAI,CAAC,SAAS;AACb,UAAM,MAAM;AAGZ,QACE,EAAE,KAAK,SAAS,QAChB,EAAE,KAAK,SAAS,QAChB,WAAW,OACX,WAAW,KACX;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,OAAO,IAAI,KAAK,KAAK;AAAA,MACrB,OAAO,OAAO,IAAI,KAAK,KAAK,MAAM,WAAY,IAAI,KAAK,KAAK,IAAe;AAAA,IAAA;AAAA,EAE/E,CAAC;AACL;AAMO,SAAS,qBACd,WACA,MAC2B;AAC3B,MAAI,CAAC,MAAM,QAAQ,SAAS,UAAU,CAAA;AACtC,SAAO,UACJ,OAAO,CAAC,SAAS,SAAS,QAAQ,OAAO,SAAS,QAAQ,EAC1D,IAAI,CAAC,SAAS;AACb,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,CAAC,KAAK,KAAK,GAAG,IAAI;AAAA,MAClB,CAAC,KAAK,KAAK,GAAG,IAAI;AAAA,IAAA;AAAA,EAEtB,CAAC;AACL;AAOO,SAAS,cACd,GACA,GACA,eACS;AACT,MAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ;AACnE,WAAO;AAAA,EACT;AACA,QAAM,MAAM,IAAI;AAAA,IACd,EACG,OAAO,CAAC,MAAM,MAAM,QAAQ,OAAO,MAAM,QAAQ,EACjD,IAAI,CAAC,MAAO,EAA8B,aAAa,CAAC;AAAA,EAAA;AAE7D,SAAO,EAAE,MAAM,CAAC,MAAM;AACpB,QAAI,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AAChD,WAAO,IAAI,IAAK,EAA8B,aAAa,CAAC;AAAA,EAC9D,CAAC;AACH;AAMO,SAAS,mBAEd,aAEA,YAEiC;AACjC,MAAI,CAAC,eAAe,OAAO,gBAAgB,SAAU,QAAO;AAC5D,QAAM,QAAQ,YAAY;AAC1B,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,WAAO;AAAA,EACT;AACA,SAAO,MAAM,OAAO,UAAU;AAChC;AAYO,SAAS,gBAEd,aACwB;AACxB,MAAI,CAAC,eAAe,YAAY,SAAS,SAAU,QAAO;AAC1D,QAAM,WAAW,YAAY;AAC7B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO;AAC9D,QAAM,CAAC,GAAG,CAAC,IAAI;AACf,QAAM,QAAS,YAAY,cAAc,CAAA;AAIzC,QAAM,UAAU,MAAM,CAAC,GAAG,WAAW;AACrC,QAAM,UAAU,MAAM,CAAC,GAAG,WAAW;AACrC,MAAI,WAAW,CAAC,QAAS,QAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AACnD,MAAI,WAAW,CAAC,QAAS,QAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AACnD,SAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AAC5B;AAQO,SAAS,kBACd,iBAEA,aACwB;AACxB,QAAM,WAAW,iBAAiB;AAClC,MACE,YACA,OAAO,SAAS,UAAU,YAC1B,OAAO,SAAS,UAAU,UAC1B;AACA,WAAO,EAAE,OAAO,SAAS,OAAO,OAAO,SAAS,MAAA;AAAA,EAClD;AACA,SAAO,gBAAgB,WAAW;AACpC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "3.0.0-beta.22",
3
+ "version": "3.0.0-beta.24",
4
4
  "description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
5
5
  "type": "module",
6
6
  "author": "Narrative I/O",
@@ -61,11 +61,17 @@ export function seedProjectionTargets(
61
61
  const dataPath = scopeToDataPath(scope);
62
62
  const segments = parseProjectionPath(projection);
63
63
 
64
- // Seed each numeric prefix in the projection path. For
65
- // `projection: '0.bars.0.name'`, that means seeding both
66
- // `<dataPath>.0` and `<dataPath>.0.bars.0`.
64
+ // Seed only when a numeric segment is followed by a *string* segment —
65
+ // i.e., the consumer is reading a property of the array item, so the
66
+ // item must be an object. Numeric-at-end (`'0'`) addresses the array
67
+ // element itself, which can be any type (primitive, nested array,
68
+ // object) — we can't infer it from the uischema alone, so leave it
69
+ // alone. Numeric-followed-by-numeric (`'0.0'`) is a nested array; we'd
70
+ // need to seed `[]`, but again the inner item type is unknown.
67
71
  for (let i = 0; i < segments.length; i++) {
68
72
  if (typeof segments[i] !== "number") continue;
73
+ if (i + 1 >= segments.length) continue;
74
+ if (typeof segments[i + 1] !== "string") continue;
69
75
 
70
76
  const partial = segments
71
77
  .slice(0, i + 1)
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export {
39
39
  ProviderAutocomplete,
40
40
  ProviderSelect,
41
41
  ProviderMultiSelect,
42
+ ProviderObjectMultiSelect,
42
43
  useProvider,
43
44
  createDataLayer,
44
45
  useDataLayer,
@@ -0,0 +1,169 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement, JsonSchema } from "@jsonforms/core";
3
+ import { useJsonFormsControl } from "@jsonforms/vue";
4
+ import { computed, inject, watch } from "vue";
5
+ import { useProvider } from "../composables/useProvider";
6
+ import { useProjection } from "../composables/useProjection";
7
+ import { resolvePlaceholder } from "../utils/placeholder";
8
+ import {
9
+ fromMultiSelectShape,
10
+ resolveItemsSchema,
11
+ resolveObjectKeys,
12
+ sameObjectSet,
13
+ toMultiSelectShape,
14
+ type MultiSelectOption,
15
+ type ObjectKeys,
16
+ } from "../utils/objectMultiSelect";
17
+ import MultiSelect from "primevue/multiselect";
18
+
19
+ const props = defineProps<{
20
+ uischema: ControlElement;
21
+ schema: JsonSchema;
22
+ path: string;
23
+ }>();
24
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
25
+ const {
26
+ projectedData,
27
+ projectedLabel,
28
+ handleProjectedChange: handleChange,
29
+ } = useProjection(control, rawHandleChange);
30
+
31
+ // Pull root schema so item-schema $refs resolve against the consumer's $defs.
32
+ const jsonforms = inject<{
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ core?: { schema?: Record<string, any> };
35
+ } | null>("jsonforms", null);
36
+ const rootSchema = computed(
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ () => (jsonforms?.core?.schema ?? control.value.schema) as Record<string, any>,
39
+ );
40
+
41
+ const itemsSchema = computed(() =>
42
+ resolveItemsSchema(
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ control.value.schema as Record<string, any>,
45
+ rootSchema.value,
46
+ ),
47
+ );
48
+
49
+ const objectKeys = computed<ObjectKeys>(() => {
50
+ const resolved = resolveObjectKeys(
51
+ control.value.uischema?.options as
52
+ | { objectKeys?: { value?: unknown; label?: unknown } }
53
+ | undefined,
54
+ itemsSchema.value,
55
+ );
56
+ if (!resolved) {
57
+ throw new Error(
58
+ "[ProviderObjectMultiSelect] objectKeys could not be resolved. " +
59
+ "Specify `uischema.options.objectKeys = { value, label }` or declare " +
60
+ "exactly two `required` properties on the array's items schema so " +
61
+ "they can be inferred.",
62
+ );
63
+ }
64
+ return resolved;
65
+ });
66
+
67
+ const binding = computed(() => {
68
+ const provider = control.value.uischema?.options?.provider;
69
+ if (provider && typeof provider === "object" && !provider.load) {
70
+ return { ...provider, load: "mount" };
71
+ }
72
+ return provider;
73
+ });
74
+
75
+ const deps = computed(
76
+ () =>
77
+ ((
78
+ (control.value.schema as Record<string, unknown>)?.[
79
+ "x-provider"
80
+ ] as Record<string, unknown>
81
+ )?.dependsOn as string[]) ?? [],
82
+ );
83
+ const depValues = computed(() => deps.value.map(() => null));
84
+
85
+ const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
86
+ const rootData = computed(() => injectedFormData.value || {});
87
+
88
+ const { items, loading, error } = useProvider(binding, {
89
+ data: rootData,
90
+ path: control.value.path,
91
+ dependsOnValues: depValues,
92
+ });
93
+
94
+ // Auto-select when provider returns only one item — paired-object variant.
95
+ watch(
96
+ [items, loading],
97
+ ([newItems, isLoading]) => {
98
+ if (
99
+ !control.value.uischema?.options?.autoSelectSingle ||
100
+ isLoading ||
101
+ newItems.length !== 1
102
+ ) {
103
+ return;
104
+ }
105
+ const single = newItems[0]!;
106
+ const current = Array.isArray(projectedData.value) ? projectedData.value : [];
107
+ if (current.length === 0) {
108
+ handleChange(control.value.path, [
109
+ {
110
+ [objectKeys.value.value]: single.value,
111
+ [objectKeys.value.label]: single.label,
112
+ },
113
+ ]);
114
+ }
115
+ },
116
+ { immediate: true },
117
+ );
118
+
119
+ const value = computed<MultiSelectOption[]>({
120
+ get() {
121
+ return toMultiSelectShape(projectedData.value, objectKeys.value);
122
+ },
123
+ set(val) {
124
+ const next = fromMultiSelectShape(val, objectKeys.value);
125
+ const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
126
+ if (!sameObjectSet(curr, next, objectKeys.value.value)) {
127
+ handleChange(control.value.path, next);
128
+ }
129
+ },
130
+ });
131
+
132
+ const placeholder = computed(() => {
133
+ if (loading.value) return "Loading…";
134
+ return resolvePlaceholder(
135
+ control.value.uischema,
136
+ projectedLabel.value,
137
+ "select",
138
+ );
139
+ });
140
+ </script>
141
+
142
+ <template>
143
+ <div class="jf-control">
144
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
145
+ <div v-if="control.description" class="jf-description">
146
+ {{ control.description }}
147
+ </div>
148
+ <MultiSelect
149
+ v-model="value"
150
+ class="w-full!"
151
+ :options="items"
152
+ option-label="label"
153
+ data-key="value"
154
+ display="chip"
155
+ :placeholder="placeholder"
156
+ :disabled="!control.enabled || loading"
157
+ :show-clear="true"
158
+ />
159
+ <small v-if="error" class="p-error" role="alert"
160
+ >Failed to load: {{ error }}</small
161
+ >
162
+ </div>
163
+ </template>
164
+
165
+ <style scoped>
166
+ :deep(.p-multiselect-label) {
167
+ text-align: left;
168
+ }
169
+ </style>
package/src/vue/index.ts CHANGED
@@ -9,9 +9,11 @@ import {
9
9
  isControl,
10
10
  } from "@jsonforms/core";
11
11
  import { resolveScopeSchema } from "../core/resolveScope";
12
+ import { resolveItemsSchema } from "./utils/objectMultiSelect";
12
13
  import ProviderAutocomplete from "./components/ProviderAutocomplete.vue";
13
14
  import ProviderSelect from "./components/ProviderSelect.vue";
14
15
  import ProviderMultiSelect from "./components/ProviderMultiSelect.vue";
16
+ import ProviderObjectMultiSelect from "./components/ProviderObjectMultiSelect.vue";
15
17
 
16
18
  // Custom tester that checks if provider option exists (as object or boolean)
17
19
  const hasProvider = (uischema: UISchemaElement) => {
@@ -60,27 +62,62 @@ const providerAutocompleteTester = rankWith(
60
62
  ),
61
63
  );
62
64
 
63
- // Custom array tester - supports nested scope paths
64
- const isArrayControl = (uischema: UISchemaElement, schema: unknown) => {
65
+ // Resolve the array schema at a control's scope, or undefined if the
66
+ // control isn't an array. Shared by both array testers below.
67
+ const getArraySchema = (uischema: UISchemaElement, schema: unknown) => {
65
68
  const controlSchema = uischema as { type: string; scope?: string };
66
- if (controlSchema.type !== "Control" || !controlSchema.scope) {
67
- return false;
68
- }
69
-
69
+ if (controlSchema.type !== "Control" || !controlSchema.scope) return undefined;
70
70
  const propertySchema = resolveScopeSchema(
71
71
  controlSchema.scope,
72
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
73
  schema as Record<string, any>,
74
74
  );
75
- return propertySchema?.type === "array";
75
+ if (propertySchema?.type !== "array") return undefined;
76
+ return propertySchema;
77
+ };
78
+
79
+ // Object-item arrays — render with paired-object MultiSelect.
80
+ const isObjectArrayControl = (uischema: UISchemaElement, schema: unknown) => {
81
+ const arr = getArraySchema(uischema, schema);
82
+ if (!arr) return false;
83
+ const items = resolveItemsSchema(
84
+ arr,
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ schema as Record<string, any>,
87
+ );
88
+ return items?.type === "object";
76
89
  };
77
90
 
91
+ // Scalar-item arrays — the original tester. Excludes object-item arrays so
92
+ // the new ObjectMultiSelect renderer can claim them at a higher rank.
93
+ const isScalarArrayControl = (uischema: UISchemaElement, schema: unknown) => {
94
+ const arr = getArraySchema(uischema, schema);
95
+ if (!arr) return false;
96
+ const items = resolveItemsSchema(
97
+ arr,
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ schema as Record<string, any>,
100
+ );
101
+ // Treat unknown / no-items shapes as scalar (matches prior behavior where
102
+ // any array control was eligible for ProviderMultiSelect).
103
+ return items?.type !== "object";
104
+ };
105
+
106
+ const providerObjectMultiSelectTester = rankWith(
107
+ 110, // One higher than scalar multi-select so object-item arrays match first.
108
+ and(isObjectArrayControl, hasProvider),
109
+ );
110
+
78
111
  const providerMultiSelectTester = rankWith(
79
- 109, // Highest priority for array controls with providers
80
- and(isArrayControl, hasProvider),
112
+ 109,
113
+ and(isScalarArrayControl, hasProvider),
81
114
  );
82
115
 
83
116
  export const providerRenderers = [
117
+ {
118
+ tester: providerObjectMultiSelectTester,
119
+ renderer: ProviderObjectMultiSelect,
120
+ },
84
121
  { tester: providerMultiSelectTester, renderer: ProviderMultiSelect },
85
122
  { tester: providerAutocompleteTester, renderer: ProviderAutocomplete },
86
123
  { tester: providerSelectTester, renderer: ProviderSelect },
@@ -90,7 +127,12 @@ export const providerRenderers = [
90
127
  export { primevueRenderers, registerPrimevueRenderers } from "./primevue";
91
128
 
92
129
  // Export individual components
93
- export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect };
130
+ export {
131
+ ProviderAutocomplete,
132
+ ProviderSelect,
133
+ ProviderMultiSelect,
134
+ ProviderObjectMultiSelect,
135
+ };
94
136
  export { useProvider } from "./composables/useProvider";
95
137
  export { useProjection } from "./composables/useProjection";
96
138
  export type { ProjectionResult } from "./composables/useProjection";
@@ -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
+ }