@templatical/template-tools 0.45.0 → 0.47.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/README.md CHANGED
@@ -15,6 +15,10 @@ npx -y @templatical/template-tools validate .templatical/my-template.json
15
15
  | `edit <file> --op <json>` | Apply one operation, or a batch with `--ops <file>` |
16
16
  | `import <file>` | Convert a template from another tool's export format — run `import --list-formats` to see what's supported |
17
17
  | `live` | Open the template in the real editor in a browser |
18
+ | `live --custom-block <file> [--host <template>] [--fetch [--values '<json>']]` | Preview a custom block definition in the real editor, optionally inside a host template, with a state filled from the real endpoint |
19
+ | `custom-block validate <file>` | Check a custom block definition against its schema, its Liquid template, email-client safety and accessibility rules |
20
+ | `custom-block render <file>` | Render a custom block's specimen states to MJML (`--format html` for HTML, `--state <name>` for one state) |
21
+ | `custom-block fetch <file>` | Run the definition's `dataSourcePreview` recipe and print the response |
18
22
  | `schema` | Print the block JSON Schema |
19
23
  | `list` | List the working templates in `.templatical/` |
20
24
 
@@ -31,9 +35,10 @@ Add `--json` to any command for machine-readable output on stdout.
31
35
 
32
36
  ## Optional dependencies
33
37
 
34
- Nothing is installed into your project by default. Two commands can ask for one thing:
38
+ Nothing is installed into your project by default. These commands can ask for an optional package:
35
39
 
36
- - `render --format html` needs `mjml` (the SDK bundles no MJML compiler).
40
+ - `render --format html` and `custom-block render --format html` need `mjml` (the SDK bundles no MJML compiler).
41
+ - `custom-block validate` compiles to HTML when `mjml` is installed and skips that check (with a note) when it isn't.
37
42
  - `import` needs the converter package for that format. Run `import --list-formats` to see which are resolvable right now — the list grows over time, so this is the only answer that doesn't go stale.
38
43
 
39
44
  Install them wherever you run the command; they are resolved from your working directory.
@@ -41,8 +46,15 @@ Install them wherever you run the command; they are resolved from your working d
41
46
  ## Library use
42
47
 
43
48
  ```ts
44
- import { validateTemplate, runQualityLint, applyOperation } from "@templatical/template-tools";
49
+ import {
50
+ validateTemplate,
51
+ runQualityLint,
52
+ applyOperation,
53
+ checkCustomBlock,
54
+ } from "@templatical/template-tools";
45
55
  import { startBridge } from "@templatical/template-tools/live";
46
56
  ```
47
57
 
58
+ `checkCustomBlock` runs the same checks as `custom-block validate`, on a JSON working file or the object a definition module exports; `validateCustomBlockDefinition` runs only the structural check. The JSON Schema for a custom block definition is at `@templatical/template-tools/custom-block-schema.json`.
59
+
48
60
  MIT.
@@ -0,0 +1,459 @@
1
+ {
2
+ "$id": "https://templatical.com/schema/custom-block-definition.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "$ref": "#/definitions/CustomBlockDefinition",
5
+ "definitions": {
6
+ "CustomBlockDefinition": {
7
+ "type": "object",
8
+ "properties": {
9
+ "type": {
10
+ "type": "string"
11
+ },
12
+ "name": {
13
+ "type": "string"
14
+ },
15
+ "icon": {
16
+ "type": "string"
17
+ },
18
+ "description": {
19
+ "type": "string"
20
+ },
21
+ "fields": {
22
+ "type": "array",
23
+ "items": {
24
+ "$ref": "#/definitions/CustomBlockField"
25
+ }
26
+ },
27
+ "template": {
28
+ "type": "string"
29
+ },
30
+ "defaultStyles": {
31
+ "type": "object",
32
+ "properties": {
33
+ "padding": {
34
+ "$ref": "#/definitions/SpacingValue"
35
+ },
36
+ "backgroundColor": {
37
+ "type": "string"
38
+ }
39
+ },
40
+ "additionalProperties": false,
41
+ "description": "Default block styles applied when a new instance of this custom block is created. Deep-merged over the built-in defaults — only specify the fields you want to override. Controls both the editor canvas wrapper and the rendered MJML/email output."
42
+ },
43
+ "stylesheet": {
44
+ "type": "string",
45
+ "description": "Optional CSS rules attached to this custom block definition. Emitted once (deduped across instances) into `<mj-head><mj-style>…</mj-style></mj-head>` in the rendered MJML, and adopted into the editor canvas (shadow root or light-DOM mount) so authored responsive/hover/font behavior previews inside the editor.\n\nUse this for media queries, hover states, or any CSS that should apply once per definition rather than per block instance. Class names are not scoped by the SDK — namespace them yourself (e.g. `.tplc-<type>-<el>`) to avoid collisions with other definitions or built-in editor styles."
46
+ }
47
+ },
48
+ "required": [
49
+ "type",
50
+ "name",
51
+ "fields",
52
+ "template"
53
+ ],
54
+ "additionalProperties": false
55
+ },
56
+ "CustomBlockField": {
57
+ "anyOf": [
58
+ {
59
+ "$ref": "#/definitions/CustomBlockTextField"
60
+ },
61
+ {
62
+ "$ref": "#/definitions/CustomBlockTextareaField"
63
+ },
64
+ {
65
+ "$ref": "#/definitions/CustomBlockImageField"
66
+ },
67
+ {
68
+ "$ref": "#/definitions/CustomBlockColorField"
69
+ },
70
+ {
71
+ "$ref": "#/definitions/CustomBlockNumberField"
72
+ },
73
+ {
74
+ "$ref": "#/definitions/CustomBlockSelectField"
75
+ },
76
+ {
77
+ "$ref": "#/definitions/CustomBlockBooleanField"
78
+ },
79
+ {
80
+ "$ref": "#/definitions/CustomBlockRepeatableField"
81
+ }
82
+ ]
83
+ },
84
+ "CustomBlockTextField": {
85
+ "type": "object",
86
+ "properties": {
87
+ "key": {
88
+ "type": "string"
89
+ },
90
+ "label": {
91
+ "type": "string"
92
+ },
93
+ "required": {
94
+ "type": "boolean"
95
+ },
96
+ "placeholder": {
97
+ "type": "string"
98
+ },
99
+ "readOnly": {
100
+ "type": "boolean"
101
+ },
102
+ "type": {
103
+ "type": "string",
104
+ "const": "text"
105
+ },
106
+ "default": {
107
+ "type": "string"
108
+ }
109
+ },
110
+ "required": [
111
+ "key",
112
+ "label",
113
+ "type"
114
+ ],
115
+ "additionalProperties": false
116
+ },
117
+ "CustomBlockTextareaField": {
118
+ "type": "object",
119
+ "properties": {
120
+ "key": {
121
+ "type": "string"
122
+ },
123
+ "label": {
124
+ "type": "string"
125
+ },
126
+ "required": {
127
+ "type": "boolean"
128
+ },
129
+ "placeholder": {
130
+ "type": "string"
131
+ },
132
+ "readOnly": {
133
+ "type": "boolean"
134
+ },
135
+ "type": {
136
+ "type": "string",
137
+ "const": "textarea"
138
+ },
139
+ "default": {
140
+ "type": "string"
141
+ }
142
+ },
143
+ "required": [
144
+ "key",
145
+ "label",
146
+ "type"
147
+ ],
148
+ "additionalProperties": false
149
+ },
150
+ "CustomBlockImageField": {
151
+ "type": "object",
152
+ "properties": {
153
+ "key": {
154
+ "type": "string"
155
+ },
156
+ "label": {
157
+ "type": "string"
158
+ },
159
+ "required": {
160
+ "type": "boolean"
161
+ },
162
+ "placeholder": {
163
+ "type": "string"
164
+ },
165
+ "readOnly": {
166
+ "type": "boolean"
167
+ },
168
+ "type": {
169
+ "type": "string",
170
+ "const": "image"
171
+ },
172
+ "default": {
173
+ "type": "string"
174
+ }
175
+ },
176
+ "required": [
177
+ "key",
178
+ "label",
179
+ "type"
180
+ ],
181
+ "additionalProperties": false
182
+ },
183
+ "CustomBlockColorField": {
184
+ "type": "object",
185
+ "properties": {
186
+ "presets": {
187
+ "type": "array",
188
+ "items": {
189
+ "type": "string"
190
+ },
191
+ "description": "Preset colors rendered as a clickable grid inside every color picker popover. Each entry is a hex string (`'#0b5cff'`); clicking one applies it, and the preset matching the current value is marked selected (matched case-insensitively). The grid supplements the wheel and hex input unless `allowCustom` is `false`."
192
+ },
193
+ "allowCustom": {
194
+ "type": "boolean",
195
+ "description": "Allow free-form color entry — the wheel and hex input — alongside the presets. Defaults to `true`.\n\nSet to `false` together with `presets` to lock authors to the preset palette: the popover then offers only the preset grid, with no wheel or hex field (a white-label / brand-kit constraint). Ignored with a warning when no `presets` are configured — hiding the only inputs would leave the picker with no way to choose a color."
196
+ },
197
+ "key": {
198
+ "type": "string"
199
+ },
200
+ "label": {
201
+ "type": "string"
202
+ },
203
+ "required": {
204
+ "type": "boolean"
205
+ },
206
+ "placeholder": {
207
+ "type": "string"
208
+ },
209
+ "readOnly": {
210
+ "type": "boolean"
211
+ },
212
+ "type": {
213
+ "type": "string",
214
+ "const": "color"
215
+ },
216
+ "default": {
217
+ "type": "string"
218
+ }
219
+ },
220
+ "required": [
221
+ "key",
222
+ "label",
223
+ "type"
224
+ ],
225
+ "additionalProperties": false,
226
+ "description": "A color-picker field. Beyond the shared field props it accepts the same flat `presets` / `allowCustom` pair as the editor-wide `colors` config (their shapes are derived from `ColorsConfig`, so the two can't drift), scoping this one field to a color *role* — e.g. a button-color field offering only the brand's accent/ink pair while other color fields inherit the global palette.\n\n**Narrowing only — of the author's freedom, not the palette.** A field can restrict what authors may do (lock this field, or give it its own `presets`), but never unlock what the editor locked. Its `presets` *replace* the editor-wide grid rather than intersecting it, so a locked field may offer colors outside `colors.presets`:\n\n- `presets` — entries are validated exactly like editor-level presets (`#rgb` / `#rrggbb` hex; invalid ones are skipped with a console warning naming the block and field). A non-empty valid list replaces the editor's palette for this field; leaving it unset, passing `[]`, or passing only invalid entries all inherit the editor's palette.\n- `allowCustom` — `false` locks this field to its palette even while the rest of the editor allows free-form entry. `true` cannot unlock a field when the editor-wide `colors.allowCustom` is `false`; it is ignored with a warning."
227
+ },
228
+ "CustomBlockNumberField": {
229
+ "type": "object",
230
+ "properties": {
231
+ "key": {
232
+ "type": "string"
233
+ },
234
+ "label": {
235
+ "type": "string"
236
+ },
237
+ "required": {
238
+ "type": "boolean"
239
+ },
240
+ "placeholder": {
241
+ "type": "string"
242
+ },
243
+ "readOnly": {
244
+ "type": "boolean"
245
+ },
246
+ "type": {
247
+ "type": "string",
248
+ "const": "number"
249
+ },
250
+ "default": {
251
+ "type": "number"
252
+ },
253
+ "min": {
254
+ "type": "number"
255
+ },
256
+ "max": {
257
+ "type": "number"
258
+ },
259
+ "step": {
260
+ "type": "number"
261
+ }
262
+ },
263
+ "required": [
264
+ "key",
265
+ "label",
266
+ "type"
267
+ ],
268
+ "additionalProperties": false
269
+ },
270
+ "CustomBlockSelectField": {
271
+ "type": "object",
272
+ "properties": {
273
+ "key": {
274
+ "type": "string"
275
+ },
276
+ "label": {
277
+ "type": "string"
278
+ },
279
+ "required": {
280
+ "type": "boolean"
281
+ },
282
+ "placeholder": {
283
+ "type": "string"
284
+ },
285
+ "readOnly": {
286
+ "type": "boolean"
287
+ },
288
+ "type": {
289
+ "type": "string",
290
+ "const": "select"
291
+ },
292
+ "options": {
293
+ "type": "array",
294
+ "items": {
295
+ "$ref": "#/definitions/SelectOption"
296
+ }
297
+ },
298
+ "default": {
299
+ "type": "string"
300
+ }
301
+ },
302
+ "required": [
303
+ "key",
304
+ "label",
305
+ "options",
306
+ "type"
307
+ ],
308
+ "additionalProperties": false
309
+ },
310
+ "SelectOption": {
311
+ "type": "object",
312
+ "properties": {
313
+ "label": {
314
+ "type": "string"
315
+ },
316
+ "value": {
317
+ "type": "string"
318
+ }
319
+ },
320
+ "required": [
321
+ "label",
322
+ "value"
323
+ ],
324
+ "additionalProperties": false
325
+ },
326
+ "CustomBlockBooleanField": {
327
+ "type": "object",
328
+ "properties": {
329
+ "key": {
330
+ "type": "string"
331
+ },
332
+ "label": {
333
+ "type": "string"
334
+ },
335
+ "required": {
336
+ "type": "boolean"
337
+ },
338
+ "placeholder": {
339
+ "type": "string"
340
+ },
341
+ "readOnly": {
342
+ "type": "boolean"
343
+ },
344
+ "type": {
345
+ "type": "string",
346
+ "const": "boolean"
347
+ },
348
+ "default": {
349
+ "type": "boolean"
350
+ }
351
+ },
352
+ "required": [
353
+ "key",
354
+ "label",
355
+ "type"
356
+ ],
357
+ "additionalProperties": false
358
+ },
359
+ "CustomBlockRepeatableField": {
360
+ "type": "object",
361
+ "properties": {
362
+ "key": {
363
+ "type": "string"
364
+ },
365
+ "label": {
366
+ "type": "string"
367
+ },
368
+ "required": {
369
+ "type": "boolean"
370
+ },
371
+ "placeholder": {
372
+ "type": "string"
373
+ },
374
+ "readOnly": {
375
+ "type": "boolean"
376
+ },
377
+ "type": {
378
+ "type": "string",
379
+ "const": "repeatable"
380
+ },
381
+ "fields": {
382
+ "type": "array",
383
+ "items": {
384
+ "$ref": "#/definitions/CustomBlockRepeatableItemField"
385
+ }
386
+ },
387
+ "default": {
388
+ "type": "array",
389
+ "items": {
390
+ "type": "object",
391
+ "additionalProperties": {}
392
+ }
393
+ },
394
+ "minItems": {
395
+ "type": "number"
396
+ },
397
+ "maxItems": {
398
+ "type": "number"
399
+ }
400
+ },
401
+ "required": [
402
+ "fields",
403
+ "key",
404
+ "label",
405
+ "type"
406
+ ],
407
+ "additionalProperties": false
408
+ },
409
+ "SpacingValue": {
410
+ "type": "object",
411
+ "properties": {
412
+ "top": {
413
+ "type": "number"
414
+ },
415
+ "right": {
416
+ "type": "number"
417
+ },
418
+ "bottom": {
419
+ "type": "number"
420
+ },
421
+ "left": {
422
+ "type": "number"
423
+ }
424
+ },
425
+ "required": [
426
+ "top",
427
+ "right",
428
+ "bottom",
429
+ "left"
430
+ ],
431
+ "additionalProperties": false
432
+ },
433
+ "CustomBlockRepeatableItemField": {
434
+ "anyOf": [
435
+ {
436
+ "$ref": "#/definitions/CustomBlockTextField"
437
+ },
438
+ {
439
+ "$ref": "#/definitions/CustomBlockTextareaField"
440
+ },
441
+ {
442
+ "$ref": "#/definitions/CustomBlockImageField"
443
+ },
444
+ {
445
+ "$ref": "#/definitions/CustomBlockColorField"
446
+ },
447
+ {
448
+ "$ref": "#/definitions/CustomBlockNumberField"
449
+ },
450
+ {
451
+ "$ref": "#/definitions/CustomBlockSelectField"
452
+ },
453
+ {
454
+ "$ref": "#/definitions/CustomBlockBooleanField"
455
+ }
456
+ ]
457
+ }
458
+ }
459
+ }