@widgetic/editor 4.0.9 → 4.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +118 -0
  2. package/dist/components/WidgetPreview.svelte +65 -27
  3. package/dist/components/WidgetPreview.svelte.d.ts +8 -0
  4. package/dist/components/editors/CompositionEditorApp.svelte +508 -0
  5. package/dist/components/editors/CompositionEditorApp.svelte.d.ts +75 -0
  6. package/dist/components/editors/PropsEditor.svelte +193 -792
  7. package/dist/components/editors/PropsEditor.svelte.d.ts +7 -0
  8. package/dist/components/ic/BrowserInputController.svelte +21 -2
  9. package/dist/components/ic/ColorPickerInputController.svelte +24 -6
  10. package/dist/components/ic/DateFieldInputController.svelte +7 -3
  11. package/dist/components/ic/DatePickerInputController.svelte +10 -4
  12. package/dist/components/ic/DropdownInputController.svelte +11 -4
  13. package/dist/components/ic/FontInputController.svelte +68 -44
  14. package/dist/components/ic/InputControllerHeader.svelte +11 -8
  15. package/dist/components/ic/NumberInputController.svelte +4 -2
  16. package/dist/components/ic/PositionInputController.svelte +14 -11
  17. package/dist/components/ic/RangeCalendarInputController.svelte +10 -4
  18. package/dist/components/ic/RoundedCornersInputController.svelte +17 -9
  19. package/dist/components/ic/TextAreaInputController.svelte +20 -7
  20. package/dist/components/ic/TextAreaPreviewInputController.svelte +19 -69
  21. package/dist/components/ic/TimeInputController.svelte +9 -3
  22. package/dist/components/ic/VectorInputController.svelte +80 -54
  23. package/dist/components/ic/shared/ItemGrid.svelte +5 -1
  24. package/dist/composition/compositionApi.d.ts +95 -0
  25. package/dist/composition/compositionApi.js +289 -0
  26. package/dist/composition/compositionEditorController.d.ts +254 -0
  27. package/dist/composition/compositionEditorController.js +734 -0
  28. package/dist/composition/compositionHelpers.d.ts +72 -0
  29. package/dist/composition/compositionHelpers.js +403 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.js +4 -0
  32. package/dist/mediaLibraryContext.d.ts +10 -0
  33. package/dist/mediaLibraryContext.js +30 -0
  34. package/dist/utils/FontManager.d.ts +21 -5
  35. package/dist/utils/FontManager.js +62 -67
  36. package/dist/utils/VectorManager.d.ts +0 -1
  37. package/dist/utils/VectorManager.js +10 -12
  38. package/dist/utils/editorStorage.d.ts +23 -0
  39. package/dist/utils/editorStorage.js +95 -0
  40. package/dist/utils/fontUtils.js +5 -3
  41. package/dist/utils/htmlSafety.d.ts +21 -0
  42. package/dist/utils/htmlSafety.js +38 -0
  43. package/dist/utils/itemReorder.d.ts +90 -0
  44. package/dist/utils/itemReorder.js +354 -0
  45. package/dist/utils/recentColors.js +4 -2
  46. package/dist/utils/richTextPreview.d.ts +38 -0
  47. package/dist/utils/richTextPreview.js +207 -0
  48. package/dist/utils/scrollActivity.d.ts +14 -0
  49. package/dist/utils/scrollActivity.js +63 -0
  50. package/dist/utils/shadowDom.d.ts +67 -0
  51. package/dist/utils/shadowDom.js +211 -0
  52. package/dist/utils/vectorUtils.js +5 -3
  53. package/dist/utils/visibilityCondition.d.ts +40 -0
  54. package/dist/utils/visibilityCondition.js +535 -0
  55. package/package.json +6 -5
@@ -0,0 +1,535 @@
1
+ /**
2
+ * Decides, from the condition a widget's schema gives a control (`autoplay === true`), whether the control is shown.
3
+ *
4
+ * The condition is text that comes with a widget, written by whoever made the widget, and the editor can run inside
5
+ * a customer's own page. So it is never handed to `eval` or `new Function`: that would run the widget author's code
6
+ * with the rights of the customer's page (its cookies, its logged-in visitor, its API calls), and it fails outright
7
+ * under a Content-Security-Policy that does not allow `'unsafe-eval'`. It is read here instead, by a small parser for
8
+ * the part of JavaScript a condition is made of:
9
+ *
10
+ * - values: numbers, 'strings' and "strings", true, false, null, undefined, and [lists];
11
+ * - the settings the condition depends on, by name (`autoplay`), with `.name` and `[key]` to look inside them;
12
+ * - comparisons (`===` `!==` `==` `!=` `<` `<=` `>` `>=`), `&&`, `||`, `??`, `!`, `+ - * / %`, `a ? b : c`, brackets;
13
+ * - the methods `includes`, `indexOf`, `startsWith`, `endsWith`, `toLowerCase`, `toUpperCase` and `trim`.
14
+ *
15
+ * It can read the values it is given and nothing else: no globals (`window`, `document`, `Function`), no other call,
16
+ * no assignment, no template string, no `constructor` or `__proto__`.
17
+ */
18
+ /** A condition that cannot be read or cannot be worked out. */
19
+ export class ConditionError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'ConditionError';
23
+ }
24
+ }
25
+ /** Longer than any real condition; a limit so that a hostile schema cannot make the editor work for long. */
26
+ export const MAX_CONDITION_LENGTH = 1000;
27
+ /** How deeply brackets, lists, `!`s and `?:` may be nested; a limit that keeps the parser from running out of stack. */
28
+ export const MAX_CONDITION_DEPTH = 40;
29
+ function hasOwn(object, key) {
30
+ return Object.prototype.hasOwnProperty.call(object, key);
31
+ }
32
+ /**
33
+ * The symbols, longest first so that `===` is read before `==`. `++`, `--` and `**` are not part of what a condition
34
+ * can say, but they are read as the one symbol JavaScript reads them as, so that `--a` is refused (JavaScript
35
+ * decrements `a`) instead of being taken for "minus minus a".
36
+ */
37
+ const SYMBOLS = [
38
+ '===',
39
+ '!==',
40
+ '==',
41
+ '!=',
42
+ '<=',
43
+ '>=',
44
+ '&&',
45
+ '||',
46
+ '??',
47
+ '++',
48
+ '--',
49
+ '**',
50
+ '<',
51
+ '>',
52
+ '!',
53
+ '+',
54
+ '-',
55
+ '*',
56
+ '/',
57
+ '%',
58
+ '?',
59
+ ':',
60
+ '(',
61
+ ')',
62
+ '[',
63
+ ']',
64
+ '.',
65
+ ','
66
+ ];
67
+ const NUMBER_PATTERN = /^(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/;
68
+ const NAME_PATTERN = /^[\p{ID_Start}_$][\p{ID_Continue}$\u200c\u200d]*/u;
69
+ /** What a backslash in a string stands for. Anything else after a backslash is refused. */
70
+ const ESCAPES = new Map([
71
+ ['n', '\n'],
72
+ ['t', '\t'],
73
+ ['r', '\r'],
74
+ ['\\', '\\'],
75
+ ["'", "'"],
76
+ ['"', '"']
77
+ ]);
78
+ function tokenize(text) {
79
+ const tokens = [];
80
+ let position = 0;
81
+ while (position < text.length) {
82
+ const character = text[position];
83
+ if (/\s/.test(character)) {
84
+ position += 1;
85
+ continue;
86
+ }
87
+ if (character === "'" || character === '"') {
88
+ let value = '';
89
+ let cursor = position + 1;
90
+ let closed = false;
91
+ while (cursor < text.length) {
92
+ const next = text[cursor];
93
+ if (next === character) {
94
+ closed = true;
95
+ cursor += 1;
96
+ break;
97
+ }
98
+ if (next === '\\') {
99
+ const escaped = ESCAPES.get(text[cursor + 1]);
100
+ if (escaped === undefined) {
101
+ throw new ConditionError(`Unsupported escape in a string at position ${cursor}`);
102
+ }
103
+ value += escaped;
104
+ cursor += 2;
105
+ continue;
106
+ }
107
+ value += next;
108
+ cursor += 1;
109
+ }
110
+ if (!closed)
111
+ throw new ConditionError('A string is not closed');
112
+ tokens.push({ kind: 'string', value });
113
+ position = cursor;
114
+ continue;
115
+ }
116
+ const rest = text.slice(position);
117
+ const numberMatch = NUMBER_PATTERN.exec(rest);
118
+ if (numberMatch) {
119
+ tokens.push({ kind: 'number', value: Number(numberMatch[0]) });
120
+ position += numberMatch[0].length;
121
+ continue;
122
+ }
123
+ const nameMatch = NAME_PATTERN.exec(rest);
124
+ if (nameMatch) {
125
+ tokens.push({ kind: 'name', value: nameMatch[0] });
126
+ position += nameMatch[0].length;
127
+ continue;
128
+ }
129
+ const symbol = SYMBOLS.find((candidate) => rest.startsWith(candidate));
130
+ if (symbol === undefined) {
131
+ throw new ConditionError(`Unexpected character "${character}" at position ${position}`);
132
+ }
133
+ tokens.push({ kind: 'symbol', value: symbol });
134
+ position += symbol.length;
135
+ }
136
+ tokens.push({ kind: 'end' });
137
+ return tokens;
138
+ }
139
+ /** Names that stand for a value of their own, whatever the settings are called. */
140
+ const NAMED_VALUES = {
141
+ true: true,
142
+ false: false,
143
+ null: null,
144
+ undefined: undefined,
145
+ NaN: Number.NaN,
146
+ Infinity: Number.POSITIVE_INFINITY
147
+ };
148
+ const EQUALITY_OPERATORS = ['===', '!==', '==', '!='];
149
+ const COMPARISON_OPERATORS = ['<', '<=', '>', '>='];
150
+ const ADDITIVE_OPERATORS = ['+', '-'];
151
+ const MULTIPLICATIVE_OPERATORS = ['*', '/', '%'];
152
+ /** What a token is called in a message about it. */
153
+ function describe(token) {
154
+ if (token.kind === 'end')
155
+ return 'the end of the condition';
156
+ if (token.kind === 'string')
157
+ return JSON.stringify(token.value);
158
+ return `"${token.value}"`;
159
+ }
160
+ class Parser {
161
+ tokens;
162
+ position = 0;
163
+ depth = 0;
164
+ constructor(tokens) {
165
+ this.tokens = tokens;
166
+ }
167
+ parse() {
168
+ const node = this.parseConditional();
169
+ const next = this.peek();
170
+ if (next.kind !== 'end')
171
+ throw new ConditionError(`Unexpected ${describe(next)}`);
172
+ return node;
173
+ }
174
+ peek() {
175
+ return this.tokens[this.position];
176
+ }
177
+ take() {
178
+ const token = this.tokens[this.position];
179
+ // Never read past the end token: whatever is asked for there is "the end".
180
+ if (token.kind !== 'end')
181
+ this.position += 1;
182
+ return token;
183
+ }
184
+ isSymbol(...symbols) {
185
+ const token = this.peek();
186
+ return token.kind === 'symbol' && symbols.includes(token.value);
187
+ }
188
+ /** Takes the next token, which `isSymbol` has just said is a symbol. */
189
+ takeSymbol() {
190
+ const token = this.take();
191
+ return token.kind === 'symbol' ? token.value : '';
192
+ }
193
+ expectSymbol(symbol) {
194
+ const token = this.take();
195
+ if (token.kind !== 'symbol' || token.value !== symbol) {
196
+ throw new ConditionError(`Expected "${symbol}" but found ${describe(token)}`);
197
+ }
198
+ }
199
+ /** Runs `read` one level deeper, so that nesting has a limit. */
200
+ nested(read) {
201
+ this.depth += 1;
202
+ if (this.depth > MAX_CONDITION_DEPTH) {
203
+ throw new ConditionError('The condition is nested too deeply');
204
+ }
205
+ try {
206
+ return read();
207
+ }
208
+ finally {
209
+ this.depth -= 1;
210
+ }
211
+ }
212
+ parseConditional() {
213
+ return this.nested(() => {
214
+ const test = this.parseLogicalOr();
215
+ if (!this.isSymbol('?'))
216
+ return test;
217
+ this.take();
218
+ const whenTrue = this.parseConditional();
219
+ this.expectSymbol(':');
220
+ const whenFalse = this.parseConditional();
221
+ return { type: 'conditional', test, whenTrue, whenFalse };
222
+ });
223
+ }
224
+ // `??` shares `||`'s level here. JavaScript refuses to mix them without brackets, so nothing a widget can
225
+ // legitimately contain depends on which of the two binds tighter.
226
+ parseLogicalOr() {
227
+ let left = this.parseLogicalAnd();
228
+ while (this.isSymbol('||', '??')) {
229
+ const operator = this.takeSymbol();
230
+ left = { type: 'logical', operator, left, right: this.parseLogicalAnd() };
231
+ }
232
+ return left;
233
+ }
234
+ parseLogicalAnd() {
235
+ let left = this.parseEquality();
236
+ while (this.isSymbol('&&')) {
237
+ this.take();
238
+ left = { type: 'logical', operator: '&&', left, right: this.parseEquality() };
239
+ }
240
+ return left;
241
+ }
242
+ /** One level of left-to-right binary operators: `operators` between operands that `readOperand` reads. */
243
+ parseBinaryLevel(operators, readOperand) {
244
+ let left = readOperand();
245
+ while (this.isSymbol(...operators)) {
246
+ const operator = this.takeSymbol();
247
+ left = { type: 'binary', operator, left, right: readOperand() };
248
+ }
249
+ return left;
250
+ }
251
+ parseEquality() {
252
+ return this.parseBinaryLevel(EQUALITY_OPERATORS, () => this.parseComparison());
253
+ }
254
+ parseComparison() {
255
+ return this.parseBinaryLevel(COMPARISON_OPERATORS, () => this.parseAdditive());
256
+ }
257
+ parseAdditive() {
258
+ return this.parseBinaryLevel(ADDITIVE_OPERATORS, () => this.parseMultiplicative());
259
+ }
260
+ parseMultiplicative() {
261
+ return this.parseBinaryLevel(MULTIPLICATIVE_OPERATORS, () => this.parseUnary());
262
+ }
263
+ parseUnary() {
264
+ return this.nested(() => {
265
+ if (this.isSymbol('!', '-', '+')) {
266
+ const operator = this.takeSymbol();
267
+ return { type: 'unary', operator, operand: this.parseUnary() };
268
+ }
269
+ return this.parsePostfix();
270
+ });
271
+ }
272
+ /** A value followed by any number of `.name`, `[key]` and `.method(...)`. */
273
+ parsePostfix() {
274
+ let node = this.parsePrimary();
275
+ for (;;) {
276
+ if (this.isSymbol('.')) {
277
+ this.take();
278
+ const name = this.take();
279
+ if (name.kind !== 'name') {
280
+ throw new ConditionError(`Expected a name after "." but found ${describe(name)}`);
281
+ }
282
+ node = this.isSymbol('(')
283
+ ? { type: 'call', object: node, method: name.value, args: this.parseArguments() }
284
+ : { type: 'member', object: node, key: { type: 'literal', value: name.value } };
285
+ }
286
+ else if (this.isSymbol('[')) {
287
+ this.take();
288
+ const key = this.parseConditional();
289
+ this.expectSymbol(']');
290
+ node = { type: 'member', object: node, key };
291
+ }
292
+ else if (this.isSymbol('(')) {
293
+ // A call of anything that is not `something.method(...)`: `name(...)`, `a[0](...)`, `a()()`.
294
+ throw new ConditionError('Only the methods of a value can be called');
295
+ }
296
+ else {
297
+ return node;
298
+ }
299
+ }
300
+ }
301
+ /** The comma-separated items up to the closing symbol, which is taken too. */
302
+ parseItems(closing) {
303
+ const items = [];
304
+ if (!this.isSymbol(closing)) {
305
+ do {
306
+ items.push(this.parseConditional());
307
+ } while (this.isSymbol(',') && this.take());
308
+ }
309
+ this.expectSymbol(closing);
310
+ return items;
311
+ }
312
+ parseArguments() {
313
+ this.expectSymbol('(');
314
+ return this.parseItems(')');
315
+ }
316
+ parsePrimary() {
317
+ const token = this.take();
318
+ switch (token.kind) {
319
+ case 'number':
320
+ case 'string':
321
+ return { type: 'literal', value: token.value };
322
+ case 'name':
323
+ return hasOwn(NAMED_VALUES, token.value)
324
+ ? { type: 'literal', value: NAMED_VALUES[token.value] }
325
+ : { type: 'name', name: token.value };
326
+ case 'symbol':
327
+ if (token.value === '(') {
328
+ const inner = this.parseConditional();
329
+ this.expectSymbol(')');
330
+ return inner;
331
+ }
332
+ if (token.value === '[')
333
+ return { type: 'list', items: this.parseItems(']') };
334
+ throw new ConditionError(`Unexpected "${token.value}"`);
335
+ default:
336
+ throw new ConditionError('The condition ends too early');
337
+ }
338
+ }
339
+ }
340
+ // ---- Working it out -----------------------------------------------------------------------------------------------
341
+ /** Never read from a value, however the condition spells it: the way out to the language's own machinery. */
342
+ const FORBIDDEN_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
343
+ /**
344
+ * What `object[key]` is: only the value's own contents, never something it inherits. A text owns its characters and
345
+ * its `length`, a list its items and its `length`, an object its settings; a number or a boolean owns nothing, and
346
+ * JavaScript answers `undefined` for any name on those, so do we.
347
+ */
348
+ function readMember(object, key) {
349
+ if (typeof key !== 'string' && typeof key !== 'number') {
350
+ throw new ConditionError('A value can only be looked up by a name or a number');
351
+ }
352
+ const name = String(key);
353
+ if (FORBIDDEN_KEYS.has(name))
354
+ throw new ConditionError(`"${name}" cannot be read`);
355
+ const contents = Object(object);
356
+ return hasOwn(contents, name) ? contents[name] : undefined;
357
+ }
358
+ /** Calls one of the harmless methods of a text or a list; any other is refused. */
359
+ function callMethod(receiver, method, args) {
360
+ if (typeof receiver === 'string') {
361
+ switch (method) {
362
+ case 'includes':
363
+ return receiver.includes(String(args[0]));
364
+ case 'indexOf':
365
+ return receiver.indexOf(String(args[0]));
366
+ case 'startsWith':
367
+ return receiver.startsWith(String(args[0]));
368
+ case 'endsWith':
369
+ return receiver.endsWith(String(args[0]));
370
+ case 'toLowerCase':
371
+ return receiver.toLowerCase();
372
+ case 'toUpperCase':
373
+ return receiver.toUpperCase();
374
+ case 'trim':
375
+ return receiver.trim();
376
+ }
377
+ }
378
+ else if (Array.isArray(receiver)) {
379
+ if (method === 'includes')
380
+ return receiver.includes(args[0]);
381
+ if (method === 'indexOf')
382
+ return receiver.indexOf(args[0]);
383
+ }
384
+ throw new ConditionError(`"${method}" cannot be called here`);
385
+ }
386
+ function evaluateBinary(operator, left, right) {
387
+ // The arithmetic and the order comparisons do what JavaScript does with whatever they are given (`'a' < 'b'`
388
+ // compares texts); the casts only tell the type checker so.
389
+ const leftNumber = left;
390
+ const rightNumber = right;
391
+ switch (operator) {
392
+ case '===':
393
+ return left === right;
394
+ case '!==':
395
+ return left !== right;
396
+ // The loose forms are what a condition that says `==` has always meant.
397
+ case '==':
398
+ return left == right;
399
+ case '!=':
400
+ return left != right;
401
+ case '<':
402
+ return leftNumber < rightNumber;
403
+ case '<=':
404
+ return leftNumber <= rightNumber;
405
+ case '>':
406
+ return leftNumber > rightNumber;
407
+ case '>=':
408
+ return leftNumber >= rightNumber;
409
+ case '+':
410
+ return leftNumber + rightNumber;
411
+ case '-':
412
+ return leftNumber - rightNumber;
413
+ case '*':
414
+ return leftNumber * rightNumber;
415
+ case '/':
416
+ return leftNumber / rightNumber;
417
+ case '%':
418
+ return leftNumber % rightNumber;
419
+ }
420
+ }
421
+ function evaluateNode(node, values) {
422
+ switch (node.type) {
423
+ case 'literal':
424
+ return node.value;
425
+ case 'name':
426
+ if (!hasOwn(values, node.name)) {
427
+ throw new ConditionError(`"${node.name}" is not one of the settings this condition depends on`);
428
+ }
429
+ return values[node.name];
430
+ case 'list':
431
+ return node.items.map((item) => evaluateNode(item, values));
432
+ case 'unary': {
433
+ const operand = evaluateNode(node.operand, values);
434
+ if (node.operator === '!')
435
+ return !operand;
436
+ return node.operator === '-' ? -operand : +operand;
437
+ }
438
+ case 'logical': {
439
+ const left = evaluateNode(node.left, values);
440
+ if (node.operator === '&&')
441
+ return left ? evaluateNode(node.right, values) : left;
442
+ if (node.operator === '||')
443
+ return left ? left : evaluateNode(node.right, values);
444
+ return left ?? evaluateNode(node.right, values);
445
+ }
446
+ case 'conditional':
447
+ return evaluateNode(node.test, values)
448
+ ? evaluateNode(node.whenTrue, values)
449
+ : evaluateNode(node.whenFalse, values);
450
+ case 'member': {
451
+ const object = evaluateNode(node.object, values);
452
+ if (object === null || object === undefined) {
453
+ throw new ConditionError('Cannot look inside a setting that has no value');
454
+ }
455
+ return readMember(object, evaluateNode(node.key, values));
456
+ }
457
+ case 'call': {
458
+ const receiver = evaluateNode(node.object, values);
459
+ if (receiver === null || receiver === undefined) {
460
+ throw new ConditionError(`Cannot call "${node.method}" on a setting that has no value`);
461
+ }
462
+ return callMethod(receiver, node.method, node.args.map((argument) => evaluateNode(argument, values)));
463
+ }
464
+ case 'binary':
465
+ return evaluateBinary(node.operator, evaluateNode(node.left, values), evaluateNode(node.right, values));
466
+ }
467
+ }
468
+ /** Conditions already read, so a control that is checked on every change is not read again each time. */
469
+ const parsedConditions = new Map();
470
+ const MAX_REMEMBERED_CONDITIONS = 500;
471
+ function parseCondition(condition) {
472
+ const remembered = parsedConditions.get(condition);
473
+ if (remembered instanceof ConditionError)
474
+ throw remembered;
475
+ if (remembered)
476
+ return remembered;
477
+ let outcome;
478
+ try {
479
+ if (condition.length > MAX_CONDITION_LENGTH) {
480
+ throw new ConditionError(`The condition is longer than ${MAX_CONDITION_LENGTH} characters`);
481
+ }
482
+ outcome = new Parser(tokenize(condition)).parse();
483
+ }
484
+ catch (error) {
485
+ outcome = error instanceof ConditionError ? error : new ConditionError(String(error));
486
+ }
487
+ if (parsedConditions.size >= MAX_REMEMBERED_CONDITIONS)
488
+ parsedConditions.clear();
489
+ parsedConditions.set(condition, outcome);
490
+ if (outcome instanceof ConditionError)
491
+ throw outcome;
492
+ return outcome;
493
+ }
494
+ /**
495
+ * Works out a condition from the values of the settings it names, the way JavaScript would.
496
+ *
497
+ * @throws {ConditionError} when the condition cannot be read, names something that is not in `values`, or cannot be
498
+ * worked out (looking inside a setting that has no value, say).
499
+ */
500
+ export function evaluateCondition(condition, values) {
501
+ if (typeof condition !== 'string')
502
+ throw new ConditionError('A condition is text');
503
+ const parsed = parseCondition(condition);
504
+ try {
505
+ return evaluateNode(parsed, values);
506
+ }
507
+ catch (error) {
508
+ if (error instanceof ConditionError)
509
+ throw error;
510
+ // What JavaScript itself refuses to do with the values (turning an object into text, say).
511
+ throw new ConditionError(error instanceof Error ? error.message : String(error));
512
+ }
513
+ }
514
+ /** The conditions that could not be used and have been reported, so each is reported once. */
515
+ const reportedConditions = new Set();
516
+ /** A schema full of broken conditions must not fill the console, or the memory. */
517
+ const MAX_REPORTED_CONDITIONS = 50;
518
+ const MAX_LOGGED_CONDITION_LENGTH = 200;
519
+ /**
520
+ * Whether a control whose condition is `condition` is shown. A condition the editor cannot use (it is not valid, or
521
+ * names a setting it does not depend on) keeps the control shown: a mistake in a widget's schema must not hide one of
522
+ * its settings for good. The problem is reported in the console, once per condition.
523
+ */
524
+ export function conditionHolds(condition, values) {
525
+ try {
526
+ return Boolean(evaluateCondition(condition, values));
527
+ }
528
+ catch (error) {
529
+ if (reportedConditions.size < MAX_REPORTED_CONDITIONS && !reportedConditions.has(condition)) {
530
+ reportedConditions.add(condition);
531
+ console.warn(`[visibilityCondition] Could not use the condition "${String(condition).slice(0, MAX_LOGGED_CONDITION_LENGTH)}"; the control stays visible:`, error instanceof Error ? error.message : error);
532
+ }
533
+ return true;
534
+ }
535
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/editor",
3
- "version": "4.0.9",
3
+ "version": "4.0.11",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -43,6 +43,7 @@
43
43
  "deploy": "vite build && vite preview --host --port 80",
44
44
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
45
45
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
46
+ "test": "node --test \"tests/**/*.test.{js,mjs}\"",
46
47
  "lint": "prettier --check . && eslint .",
47
48
  "format": "prettier --write ."
48
49
  },
@@ -54,7 +55,6 @@
54
55
  "svelte": "^5.27.0"
55
56
  },
56
57
  "devDependencies": {
57
- "@internationalized/date": "^3.6.0",
58
58
  "@sveltejs/adapter-auto": "^3.0.0",
59
59
  "@sveltejs/adapter-static": "^3.0.10",
60
60
  "@sveltejs/kit": "^2.20.7",
@@ -79,7 +79,6 @@
79
79
  "prettier-plugin-svelte": "^3.1.2",
80
80
  "svelte-check": "^4.1.6",
81
81
  "svelte-sonner": "^1.0.3",
82
- "tailwind-merge": "^2.5.5",
83
82
  "tailwind-variants": "^0.3.0",
84
83
  "tailwindcss": "^4.1.4",
85
84
  "tailwindcss-animate": "^1.0.7",
@@ -89,8 +88,9 @@
89
88
  "vite-imagetools": "^7.0.4"
90
89
  },
91
90
  "dependencies": {
92
- "@shopify/draggable": "^1.1.3",
91
+ "@internationalized/date": "^3.6.0",
93
92
  "@sveltejs/adapter-node": "^5.2.9",
93
+ "@texel/color": "^1.1.4",
94
94
  "@tiptap/core": "^2.9.1",
95
95
  "@tiptap/extension-bubble-menu": "^2.9.1",
96
96
  "@tiptap/extension-code-block-lowlight": "^2.9.1",
@@ -123,7 +123,8 @@
123
123
  "svelte": "^5.27.0",
124
124
  "svelte-awesome-color-picker": "^3.1.4",
125
125
  "svelte-color-select": "^0.0.14",
126
- "svelte-tiptap": "^2.0.3"
126
+ "svelte-tiptap": "^2.0.3",
127
+ "tailwind-merge": "^2.5.5"
127
128
  },
128
129
  "volta": {
129
130
  "node": "22.14.0",