@streamline-pulse/formkrafter-core 0.33.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ import type { BrickType } from "../utils/brick-type.js";
2
+ /**
3
+ * The bricks a language model may write, with the configs each one reads. The
4
+ * strict schema (schema/form-spec.strict.schema.json) is generated from this
5
+ * list and fromStrictSpec reads it back, so a brick or a config a model can use
6
+ * is declared once, here. The web components test that every registered brick
7
+ * is either listed here or in AI_EXCLUDED_BRICKS, with the configs it reads.
8
+ */
9
+ export type CatalogConfig = {
10
+ kind: "text";
11
+ description: string;
12
+ } | {
13
+ kind: "string";
14
+ description: string;
15
+ } | {
16
+ kind: "number";
17
+ description: string;
18
+ } | {
19
+ kind: "options";
20
+ description: string;
21
+ };
22
+ export type CatalogDataType = "string" | "number" | "boolean" | "array" | "void";
23
+ export interface CatalogBrick {
24
+ id: string;
25
+ type: BrickType;
26
+ dataType: CatalogDataType;
27
+ /** What the brick is for, written for the model. */
28
+ description: string;
29
+ /** Configs besides key, label and description, which every field has. */
30
+ configs: Record<string, CatalogConfig>;
31
+ /** Whether the brick holds a value under configs.key. */
32
+ field: boolean;
33
+ /** "bricks": any brick; "fields": the fields of one row of a collection. */
34
+ children?: "bricks" | "fields";
35
+ }
36
+ export declare const BRICK_CATALOG: readonly CatalogBrick[];
37
+ /** Registered bricks a model does not write, and why. */
38
+ export declare const AI_EXCLUDED_BRICKS: Readonly<Record<string, string>>;
39
+ export declare const catalogBrick: (id: string) => CatalogBrick | undefined;
@@ -0,0 +1,9 @@
1
+ import type { BrickSpec } from "../utils/brick-spec.js";
2
+ /**
3
+ * Turns what a model wrote against schema/form-spec.strict.schema.json into a
4
+ * canonical spec that fk-form-render and validateFormData take unchanged: it
5
+ * unwraps { form }, drops the nulls strict mode fills absent properties with,
6
+ * restores type, dataType and name from the brick id, and expands hiddenWhen /
7
+ * disabledWhen into rules. A form whose root is not a panel gets a column.
8
+ */
9
+ export declare function fromStrictSpec(output: unknown): BrickSpec;
package/dist/index.d.ts CHANGED
@@ -3,9 +3,11 @@ export { BrickMold } from "./utils/brick-mold.js";
3
3
  export { DropType, BrickMoldDropType, BrickSpecDropType } from "./utils/drop-type.js";
4
4
  export { BrickStyles } from "./utils/brick-styles.js";
5
5
  export { BrickBaseConfigs, CommonBrickProps, WithBrickBaseConfigs } from "./utils/common-brick-props.js";
6
- export { BrickSpec, buildValidationSchema, iterateBricks, iterateSchemaBricks, defaultFormData, } from "./utils/brick-spec.js";
6
+ export { BrickLayout, BrickSpec, RowTrack, buildValidationSchema, iterateBricks, iterateSchemaBricks, defaultFormData, } from "./utils/brick-spec.js";
7
7
  export { getBrickData, wrapBrickData } from "./utils/brick-data.js";
8
8
  export { SelectOption, normalizeOptions } from "./utils/options.js";
9
+ export { BRICK_CATALOG, AI_EXCLUDED_BRICKS, CatalogBrick, CatalogConfig, CatalogDataType, catalogBrick } from "./ai/brick-catalog.js";
10
+ export { fromStrictSpec } from "./ai/from-strict-spec.js";
9
11
  export { RecapItem, collectRecapItems } from "./utils/recap.js";
10
12
  export { FieldDescriptor, CollectFieldsOptions, collectFields } from "./utils/fields.js";
11
13
  export { IdentityFormat, IDENTITY_FORMATS, checkIdentity, checkFrNir, checkBjNpi, checkDigits, checkLuhn, checkFrSiren, checkFrSiret, checkBjIfu, checkIban, formatIban } from "./utils/identity.js";
package/dist/index.js CHANGED
@@ -7130,6 +7130,154 @@ function normalizeOptions(raw, labelKey = "label", valueKey = "value") {
7130
7130
  return String(raw ?? "").split(`
7131
7131
  `).map((line) => line.trim()).filter(Boolean).map((line) => ({ label: line, value: line }));
7132
7132
  }
7133
+ // lib/ai/brick-catalog.ts
7134
+ var placeholder = { kind: "text", description: "Example shown in the empty control." };
7135
+ var options = { kind: "options", description: "The choices, in display order: label shown, value stored." };
7136
+ var field = (id, dataType, description, configs = {}) => ({ id, type: "input", dataType, description, configs, field: true });
7137
+ var BRICK_CATALOG = [
7138
+ field("text", "string", "One line of free text.", {
7139
+ placeholder,
7140
+ mask: { kind: "string", description: "Input mask: 9 a digit, a a letter, A a letter in capitals, * either; other characters are inserted as they are." }
7141
+ }),
7142
+ field("textarea", "string", "Several lines of free text.", { placeholder }),
7143
+ field("email", "string", "An email address.", { placeholder }),
7144
+ field("url", "string", "A web address (https://…).", { placeholder }),
7145
+ field("phone", "string", "A phone number.", {
7146
+ placeholder,
7147
+ mask: { kind: "string", description: "Input mask: 9 a digit, other characters inserted as they are, for example +228 99 99 99 99." }
7148
+ }),
7149
+ field("number", "number", "A number.", {
7150
+ placeholder,
7151
+ prefix: { kind: "text", description: "Shown before the control, for example a currency." },
7152
+ suffix: { kind: "text", description: "Shown after the control, for example a unit." }
7153
+ }),
7154
+ field("date", "string", "A date, stored as YYYY-MM-DD."),
7155
+ field("time", "string", "A time of day, stored as HH:MM."),
7156
+ field("datetime", "string", "A date and a time."),
7157
+ field("checkbox", "boolean", "A yes/no box; the label sits beside it. Use it for each item of a list someone ticks."),
7158
+ field("select", "string", "One choice in a drop-down list. Prefer radio for 2 to 4 short choices.", { placeholder, options }),
7159
+ field("radio", "string", "One choice among a few options, all visible.", { options }),
7160
+ field("multi-select", "array", "Several choices in a drop-down list.", { placeholder, options }),
7161
+ field("select-boxes", "array", "Several choices, one box each, all visible.", { options }),
7162
+ {
7163
+ id: "data-grid",
7164
+ type: "collection",
7165
+ dataType: "array",
7166
+ description: "A table whose rows the person adds; children are the fields of one row.",
7167
+ configs: {},
7168
+ field: true,
7169
+ children: "fields"
7170
+ },
7171
+ {
7172
+ id: "content",
7173
+ type: "output",
7174
+ dataType: "void",
7175
+ description: "Fixed text shown to the person, not a question. Put it above or below a row, not in it.",
7176
+ configs: { content: { kind: "text", description: "The text, one paragraph per line." } },
7177
+ field: false
7178
+ },
7179
+ {
7180
+ id: "group",
7181
+ type: "panel",
7182
+ dataType: "void",
7183
+ description: "A titled box around related bricks.",
7184
+ configs: { label: { kind: "text", description: "The group's title." } },
7185
+ field: false,
7186
+ children: "bricks"
7187
+ },
7188
+ {
7189
+ id: "row",
7190
+ type: "panel",
7191
+ dataType: "void",
7192
+ description: "Bricks side by side; they stack on narrow screens. Put in one row only questions shown together.",
7193
+ configs: {},
7194
+ field: false,
7195
+ children: "bricks"
7196
+ },
7197
+ {
7198
+ id: "column",
7199
+ type: "panel",
7200
+ dataType: "void",
7201
+ description: "Bricks one under the other; the usual root of a form.",
7202
+ configs: {},
7203
+ field: false,
7204
+ children: "bricks"
7205
+ }
7206
+ ];
7207
+ var AI_EXCLUDED_BRICKS = {
7208
+ password: "a form written by a model must not ask for a password",
7209
+ hidden: "carries a value the host sets, not one a person enters",
7210
+ tags: "free list of words; select-boxes or textarea cover what a model needs",
7211
+ signature: "legal capture belongs in forms a person designs",
7212
+ "geo-point": "needs the host's map tiles",
7213
+ "geo-area": "needs the host's map tiles",
7214
+ "geo-address": "needs the host's search and map services",
7215
+ "geo-layer": "needs the host's layers",
7216
+ "national-id": "country identifier formats are a design decision",
7217
+ mrz: "identity documents belong in forms a person designs",
7218
+ "tax-id": "country identifier formats are a design decision",
7219
+ iban: "bank details belong in forms a person designs",
7220
+ money: "currency lists are a design decision",
7221
+ appointment: "needs the host's slot provider",
7222
+ document: "needs the host's document reader",
7223
+ measure: "unit families are a design decision",
7224
+ "vehicle-plate": "country plate formats are a design decision",
7225
+ "company-registry": "country registry formats are a design decision",
7226
+ "date-range": "two dates cover it",
7227
+ tabs: "navigation belongs in forms a person designs",
7228
+ stepper: "navigation belongs in forms a person designs",
7229
+ address: "the host's address model is a design decision",
7230
+ file: "needs the host's upload service",
7231
+ "key-value": "free-form object, not a question",
7232
+ recap: "a summary page, not a question",
7233
+ code: "a code editor is not a question for a person",
7234
+ "nested-form": "references the host's stored forms"
7235
+ };
7236
+ var byId = new Map(BRICK_CATALOG.map((brick) => [brick.id, brick]));
7237
+ var catalogBrick = (id) => byId.get(id);
7238
+ // lib/ai/from-strict-spec.ts
7239
+ var isNode = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7240
+ var present = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== null));
7241
+ var RULE_TARGETS = { hiddenWhen: "hidden", disabledWhen: "disabled" };
7242
+ var rulesOf = (node) => Object.keys(RULE_TARGETS).filter((name) => isNode(node[name])).map((name) => ({
7243
+ name,
7244
+ type: "jsonLogic",
7245
+ logic: node[name],
7246
+ effects: [{ property: { target: RULE_TARGETS[name], type: "boolean" }, boolean: true }]
7247
+ }));
7248
+ var toBrick = (node, path) => {
7249
+ if (!isNode(node))
7250
+ throw new Error(`fromStrictSpec: ${path} is not a brick`);
7251
+ const id = String(node.id);
7252
+ const entry = catalogBrick(id);
7253
+ if (!entry)
7254
+ throw new Error(`fromStrictSpec: ${path} has an unknown brick id "${id}"`);
7255
+ const configs = isNode(node.configs) ? present(node.configs) : {};
7256
+ if ("options" in configs && (id === "select" || id === "multi-select"))
7257
+ configs.optionsSource = "static";
7258
+ const validations = Array.isArray(node.validations) ? node.validations.filter(isNode).map((validation2) => present(validation2)) : [];
7259
+ const rules = rulesOf(node);
7260
+ const layout = isNode(node.layout) ? present(node.layout) : {};
7261
+ const children = Array.isArray(node.children) ? node.children.map((child, index) => toBrick(child, `${path}.${index}`)) : [];
7262
+ return {
7263
+ type: entry.type,
7264
+ dataType: entry.dataType,
7265
+ id,
7266
+ name: String(configs.label ?? configs.key ?? id),
7267
+ configs,
7268
+ ...validations.length ? { validations } : {},
7269
+ ...rules.length ? { rules } : {},
7270
+ ...Object.keys(layout).length ? { layout } : {},
7271
+ ...children.length ? { children } : {}
7272
+ };
7273
+ };
7274
+ function fromStrictSpec(output) {
7275
+ const root = isNode(output) && "form" in output ? output.form : output;
7276
+ const spec = toBrick(root, "form");
7277
+ if (spec.type === "panel")
7278
+ return spec;
7279
+ return { type: "panel", dataType: "void", id: "column", name: "Form", configs: {}, children: [spec] };
7280
+ }
7133
7281
  // lib/i18n.ts
7134
7282
  var en = {
7135
7283
  "palette.search": "Search bricks…",
@@ -8962,6 +9110,7 @@ function documentFillPatch(fields, fills) {
8962
9110
  var flaggedKeys = (annotations) => Object.entries(annotations ?? {}).filter(([, annotation]) => annotation?.incorrect === true).map(([key]) => key);
8963
9111
  // lib/utils/lint-spec.ts
8964
9112
  var isDataCarrying = (brick) => brick.type === "input" || brick.type === "collection";
9113
+ var isRow = (brick) => brick.type === "panel" && brick.id === "row";
8965
9114
  function lintSpec(spec) {
8966
9115
  if (!spec)
8967
9116
  return [];
@@ -9008,6 +9157,17 @@ function lintSpec(spec) {
9008
9157
  message: `collection "${key ?? brick.id}" has no row template — it can never produce data`
9009
9158
  });
9010
9159
  }
9160
+ if (isRow(brick)) {
9161
+ for (const [index, child] of (brick.children ?? []).entries()) {
9162
+ if (child?.type !== "output")
9163
+ continue;
9164
+ issues.push({
9165
+ code: "content-in-row",
9166
+ path: `${path}.${index}`,
9167
+ message: `${child.id} brick in a row takes a column like a field and reads as a label without one — place it above or below the row`
9168
+ });
9169
+ }
9170
+ }
9011
9171
  }
9012
9172
  return issues;
9013
9173
  }
@@ -10065,6 +10225,8 @@ async function expandSpec(spec, options = {}) {
10065
10225
  return expandBrick(structuredClone(spec), []);
10066
10226
  }
10067
10227
  export {
10228
+ AI_EXCLUDED_BRICKS,
10229
+ BRICK_CATALOG,
10068
10230
  Base64FileUploadService,
10069
10231
  Brick,
10070
10232
  FetchDataSourceService,
@@ -10082,6 +10244,7 @@ export {
10082
10244
  addBrick,
10083
10245
  appendSearchParam,
10084
10246
  buildValidationSchema,
10247
+ catalogBrick,
10085
10248
  checkBjIfu,
10086
10249
  checkBjNpi,
10087
10250
  checkCompanyRegistry,
@@ -10113,6 +10276,7 @@ export {
10113
10276
  formatMeasure,
10114
10277
  formatPlate,
10115
10278
  frFkTranslations,
10279
+ fromStrictSpec,
10116
10280
  generateSlots,
10117
10281
  getAffectedProperties,
10118
10282
  getBrickAt,
@@ -10159,5 +10323,5 @@ export {
10159
10323
  wrapBrickData
10160
10324
  };
10161
10325
 
10162
- //# debugId=C3D4B3C74EC4E31D64756E2164756E21
10326
+ //# debugId=4A8FDDA420C6F9E564756E2164756E21
10163
10327
  //# sourceMappingURL=index.js.map