claude-artifact-framework 0.6.0 → 0.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-artifact-framework",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Utilities for building apps inside Claude Artifacts: platform storage, chat tool-calling loops, and other primitives verified against the real runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",
package/src/app.js CHANGED
@@ -36,6 +36,11 @@ function checkFields(fields, where) {
36
36
  `claude-artifact-framework: field "${f.key}" in ${where} has unknown type "${f.type}". Valid types: ${FIELD_TYPES.join(", ")}.`
37
37
  );
38
38
  }
39
+ if (f.type === "select" && f.options !== undefined && !Array.isArray(f.options) && typeof f.options !== "function") {
40
+ throw new Error(
41
+ `claude-artifact-framework: field "${f.key}" in ${where}: \`options\` must be an array or a function of data returning one.`
42
+ );
43
+ }
39
44
  }
40
45
  }
41
46
 
package/src/blocks.js CHANGED
@@ -49,7 +49,7 @@ export function validateField(field, value) {
49
49
  return null;
50
50
  }
51
51
 
52
- export function Field({ field, value, onChange }) {
52
+ export function Field({ field, value, onChange, data }) {
53
53
  const error = validateField(field, value);
54
54
  const id = `caf-f-${field.key}`;
55
55
  const numeric = field.type === "number" || field.type === "money" || field.type === "percent";
@@ -79,11 +79,14 @@ export function Field({ field, value, onChange }) {
79
79
  if (field.type === "textarea") {
80
80
  control = h("textarea", { ...common, rows: field.rows || 3 });
81
81
  } else if (field.type === "select") {
82
+ // Options may be declared as a function of the app data, for selects
83
+ // whose choices come from what the user has entered elsewhere.
84
+ const options = typeof field.options === "function" ? field.options(data) || [] : field.options || [];
82
85
  control = h(
83
86
  "select",
84
87
  common,
85
88
  h("option", { value: "" }, field.placeholder || "Select…"),
86
- (field.options || []).map((opt) => {
89
+ options.map((opt) => {
87
90
  const val = typeof opt === "string" ? opt : opt.value;
88
91
  const label = typeof opt === "string" ? opt : opt.label;
89
92
  return h("option", { key: val, value: val }, label);
@@ -126,6 +129,7 @@ export function FieldsBlock({ spec, ctx }) {
126
129
  h(Field, {
127
130
  key: field.key,
128
131
  field,
132
+ data: ctx.data,
129
133
  value: ctx.data[field.key],
130
134
  onChange: (v) => ctx.update((d) => { d[field.key] = v; }),
131
135
  })
@@ -170,13 +174,18 @@ export function TextBlock({ spec, ctx }) {
170
174
 
171
175
  export function ListBlock({ spec, ctx }) {
172
176
  const rows = (typeof spec.list === "function" ? spec.list(ctx.data) : spec.list) || [];
177
+ // `title` follows the same convention as every other block: a string is the
178
+ // block heading. A function is the per-row title. Both are supported —
179
+ // making list the odd one out was an API inconsistency a blind test caught.
180
+ const heading = typeof spec.title === "string" ? spec.title : null;
181
+ const rowTitle = typeof spec.title === "function" ? spec.title : (r) => r.title ?? r.name ?? r.label ?? r.text ?? String(r);
173
182
  if (!rows.length) {
174
183
  return h("div", { className: "caf-block caf-empty" }, spec.empty || "Nothing here yet.");
175
184
  }
176
- const title = spec.title || ((r) => r.title ?? r.name ?? r.label ?? String(r));
177
185
  return h(
178
186
  "div",
179
187
  { className: "caf-block caf-list" },
188
+ heading ? h("h2", { className: "caf-block-title" }, heading) : null,
180
189
  rows.map((row, i) =>
181
190
  h(
182
191
  "button",
@@ -186,7 +195,7 @@ export function ListBlock({ spec, ctx }) {
186
195
  type: "button",
187
196
  onClick: spec.onSelect ? () => spec.onSelect(row, ctx) : undefined,
188
197
  },
189
- h("span", { className: "caf-row-title" }, title(row)),
198
+ h("span", { className: "caf-row-title" }, rowTitle(row)),
190
199
  spec.subtitle ? h("span", { className: "caf-row-sub" }, spec.subtitle(row)) : null
191
200
  )
192
201
  )
package/src/records.js CHANGED
@@ -199,6 +199,7 @@ function DetailScreen({ spec, ctx, record }) {
199
199
  h(Field, {
200
200
  key: field.key,
201
201
  field,
202
+ data: ctx.data,
202
203
  value: record[field.key],
203
204
  onChange: (v) =>
204
205
  ctx.update((d) => {
@@ -272,6 +273,7 @@ function ItemsEditor({ spec, ctx, record }) {
272
273
  h(Field, {
273
274
  key: field.key,
274
275
  field,
276
+ data: ctx.data,
275
277
  value: item[field.key],
276
278
  onChange: (v) =>
277
279
  mutate((rec) => {
package/src/steps.js CHANGED
@@ -78,6 +78,7 @@ export function StepsLayout({ spec, ctx }) {
78
78
  h(Field, {
79
79
  key: field.key,
80
80
  field,
81
+ data: ctx.data,
81
82
  value: ctx.data[field.key],
82
83
  onChange: (v) => ctx.update((d) => { d[field.key] = v; }),
83
84
  })