@burger-editor/frozen-patty 0.11.0 → 4.0.0-alpha.10

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/dist/set-value.js CHANGED
@@ -1,136 +1,147 @@
1
+ import { kebabCase } from '@burger-editor/utils';
2
+ import { parseFields } from './parse-fields.js';
3
+ import { propInElement, replaceNode, sanitizeAttributeValue, sanitizeHtml, } from './utils.js';
1
4
  /**
2
5
  * Set value to an element
3
6
  *
4
7
  * ```html
5
8
  * <div [target-attribute] data-[attr]="[name]:[target-attribute]"></div>
6
9
  * ```
7
- *
8
10
  * @param el A target element
9
11
  * @param name A label name
10
12
  * @param datum A datum of value
11
13
  * @param attr Data attribute name for specifying the node that FrozenPatty treats as a field
14
+ * @param filter
15
+ * @param xssSanitize Enable XSS protection
12
16
  */
13
- export function setValue(el, name, datum, attr = 'field', filter) {
14
- const bindingFormats = el.getAttribute(`data-${attr}`) || '';
15
- for (let bindingFormat of bindingFormats.split(/\s*,\s*/)) {
16
- bindingFormat = bindingFormat.trim();
17
- /**
18
- * 抽出した対象キー
19
- */
20
- let key;
21
- /**
22
- * 対象属性
23
- *
24
- * `null`の場合は`innerHTML`が対象となる
25
- * ただし要素がinput要素の場合は`value`に設定
26
- */
27
- let targetAttr;
28
- //
29
- // 対象属性名の抽出
30
- //
31
- if (/^[_a-z-][\w-]*:[_a-z-][\w-]*(?:\([a-z-]+\))?/i.test(bindingFormat)) {
32
- const splitKey = bindingFormat.split(':');
33
- key = splitKey[0]?.trim() ?? '';
34
- targetAttr = splitKey[1]?.trim() ?? '';
35
- }
36
- else {
37
- key = bindingFormat.trim();
38
- targetAttr = null;
39
- }
40
- //
41
- // 対象キーが一致しなければスキップする
42
- //
43
- if (name !== key) {
44
- continue;
45
- }
46
- if (filter) {
47
- datum = filter(datum);
48
- }
49
- //
50
- // 属性による対応
51
- //
52
- if (targetAttr) {
53
- //
54
- // style属性
55
- //
56
- if (/^style\([a-z-]+\)$/i.test(targetAttr)) {
57
- const cssPropertyName = targetAttr.replace(/^style\(([a-z-]+)\)$/i, '$1');
58
- let cssValue;
59
- switch (cssPropertyName) {
60
- case 'background-image': {
61
- //
62
- // NGパターン
63
- // $changeDom.css(cssPropertyName, 'url("' + value + '")');
64
- //
65
- // cssメソッドを経由すると styleAPIを使用するので URLがホストを含めた絶対パスになる
66
- // デモサーバーから本番サーバーへの移行ができなくなってしまうので避ける
67
- // 単純な文字列を流し込む(setAttributeを利用)
68
- // urlはマルチバイト文字や空白記号を含まないはずであるがエスケープする
69
- const url = encodeURI(`${datum}`);
70
- cssValue = `url(${url})`;
71
- break;
72
- }
73
- //
74
- // TODO: 他にもvalueに単位が必要なケースなどに対応したい
75
- //
76
- default: {
77
- cssValue = `${datum}`;
78
- }
79
- }
80
- el.setAttribute('style', `${cssPropertyName}: ${cssValue}`);
81
- //
82
- // 属性
83
- //
84
- }
85
- else if (el instanceof HTMLElement) {
86
- setAttribute(el, targetAttr, datum);
87
- }
88
- else {
89
- // SVGElement or more
90
- el.setAttribute(targetAttr, `${datum}`);
17
+ export function setValue(el, name, datum, attr = 'field', filter, xssSanitize = true) {
18
+ const rawValue = el.getAttribute(`data-${attr}`);
19
+ const fields = parseFields(rawValue ?? '');
20
+ const field = fields.find((field) => field.fieldName === name);
21
+ if (!field) {
22
+ return;
23
+ }
24
+ const { propName } = field;
25
+ if (filter) {
26
+ datum = filter(datum);
27
+ }
28
+ // console.log({ name, field, fieldName, propName, datum, el: el.innerHTML });
29
+ if (!propName) {
30
+ setContent(el, datum, undefined, xssSanitize);
31
+ return;
32
+ }
33
+ if (propName === 'style' && el instanceof HTMLElement) {
34
+ el.style.cssText = `${datum}`;
35
+ return;
36
+ }
37
+ if (/^style\([a-z-]+\)$/i.test(propName)) {
38
+ const cssPropertyName = propName.replace(/^style\(([a-z-]+)\)$/i, '$1');
39
+ let cssValue;
40
+ switch (cssPropertyName) {
41
+ case 'background-image': {
42
+ // Using CSS method would create absolute URLs with host
43
+ // This would prevent migration from demo to production server
44
+ // Using simple string insertion (setAttribute) instead
45
+ // URL may not contain multibyte or whitespace characters, but escape anyway
46
+ const url = encodeURI(`${datum}`);
47
+ cssValue = `url(${url})`;
48
+ break;
91
49
  }
92
50
  //
93
- // 属性指定がない場合
51
+ // TODO: Handle other cases where values need units
94
52
  //
95
- }
96
- else {
97
- if (el instanceof HTMLInputElement ||
98
- el instanceof HTMLSelectElement ||
99
- el instanceof HTMLTextAreaElement) {
100
- el.value = `${datum}`;
101
- }
102
- else {
103
- el.innerHTML = `${datum}`;
53
+ default: {
54
+ cssValue = `${datum}`;
104
55
  }
105
56
  }
57
+ el.setAttribute('style', `${cssPropertyName}: ${cssValue}`);
58
+ return;
59
+ }
60
+ if (el instanceof HTMLElement) {
61
+ // HTMLElement
62
+ set(el, attr, propName, datum, xssSanitize);
63
+ return;
106
64
  }
65
+ // SVGElement or more
66
+ // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
67
+ // Check attribute value if XSS protection is enabled
68
+ if (xssSanitize) {
69
+ const safeValue = sanitizeAttributeValue(propName, `${datum}`);
70
+ if (safeValue !== null) {
71
+ el.setAttribute(propName, safeValue);
72
+ }
73
+ return;
74
+ }
75
+ el.setAttribute(propName, `${datum}`);
107
76
  }
108
77
  /**
109
- * Set attribute
110
- *
111
- * @param el Target HTML element
112
- * @param attr Attribute name
113
- * @param datum Datum
114
78
  *
79
+ * @param el
80
+ * @param prefix
81
+ * @param name
82
+ * @param datum
83
+ * @param xssSanitize
115
84
  */
116
- function setAttribute(el, attr, datum) {
85
+ function set(el, prefix, name, datum, xssSanitize = true) {
86
+ if (datum == null) {
87
+ el.removeAttribute(name);
88
+ return;
89
+ }
90
+ switch (name) {
91
+ case 'text': {
92
+ setContent(el, datum, false, xssSanitize);
93
+ return;
94
+ }
95
+ case 'html': {
96
+ setContent(el, datum, true, xssSanitize);
97
+ return;
98
+ }
99
+ case 'node': {
100
+ if (typeof datum !== 'string') {
101
+ return;
102
+ }
103
+ replaceNode(el, datum, prefix, xssSanitize);
104
+ return;
105
+ }
106
+ }
107
+ // If XSS protection is enabled
108
+ if (xssSanitize) {
109
+ // Don't set event handler attributes (starting with 'on')
110
+ if (name.toLowerCase().startsWith('on')) {
111
+ return;
112
+ }
113
+ const beforeSanitize = datum;
114
+ datum = sanitizeAttributeValue(name, datum);
115
+ // If sanitization results in null value (except when original value was null), don't change the value
116
+ if (datum === null && beforeSanitize !== null) {
117
+ return;
118
+ }
119
+ }
117
120
  if (datum == null) {
118
- el.removeAttribute(attr);
121
+ el.removeAttribute(name);
119
122
  return;
120
123
  }
121
- switch (attr) {
124
+ if (!name.startsWith('data-') && !propInElement(el, name)) {
125
+ const dataAttr = `data-${prefix}-${kebabCase(name)}`;
126
+ if (el.hasAttribute(dataAttr)) {
127
+ el.setAttribute(dataAttr, `${datum}`);
128
+ }
129
+ return;
130
+ }
131
+ switch (name) {
122
132
  case 'contenteditable': {
123
133
  switch (datum) {
134
+ case true:
124
135
  case 'true':
125
136
  case '': {
126
137
  el.contentEditable = 'true';
127
138
  break;
128
139
  }
129
140
  default: {
130
- el.removeAttribute(attr);
141
+ el.removeAttribute(name);
131
142
  }
132
143
  }
133
- break;
144
+ return;
134
145
  }
135
146
  case 'dir': {
136
147
  switch (datum) {
@@ -141,10 +152,10 @@ function setAttribute(el, attr, datum) {
141
152
  }
142
153
  // case 'auto':
143
154
  default: {
144
- el.removeAttribute(attr);
155
+ el.removeAttribute(name);
145
156
  }
146
157
  }
147
- break;
158
+ return;
148
159
  }
149
160
  case 'draggable': {
150
161
  if (typeof datum === 'boolean') {
@@ -162,18 +173,22 @@ function setAttribute(el, attr, datum) {
162
173
  }
163
174
  // case 'auto':
164
175
  default: {
165
- el.removeAttribute(attr);
176
+ el.removeAttribute(name);
166
177
  }
167
178
  }
168
- break;
179
+ return;
169
180
  }
170
181
  case 'hidden': {
182
+ if (datum === 'until-found') {
183
+ el.setAttribute(name, datum);
184
+ return;
185
+ }
171
186
  el.hidden = !!datum;
172
- break;
187
+ return;
173
188
  }
174
189
  case 'spellcheck': {
175
190
  el.spellcheck = !!datum;
176
- break;
191
+ return;
177
192
  }
178
193
  case 'tabindex': {
179
194
  let i;
@@ -187,16 +202,16 @@ function setAttribute(el, attr, datum) {
187
202
  i = datum;
188
203
  }
189
204
  el.tabIndex = Number.isNaN(i) ? -1 : Math.floor(i);
190
- break;
205
+ return;
191
206
  }
192
207
  case 'async': {
193
208
  if (el instanceof HTMLScriptElement) {
194
209
  el.async = !!datum;
195
210
  }
196
211
  else {
197
- el.setAttribute(attr, `${datum}`);
212
+ el.setAttribute(name, `${datum}`);
198
213
  }
199
- break;
214
+ return;
200
215
  }
201
216
  case 'autocomplete': {
202
217
  if (el instanceof HTMLInputElement || el instanceof HTMLFormElement) {
@@ -204,9 +219,9 @@ function setAttribute(el, attr, datum) {
204
219
  el.autocomplete = datum ? `${datum}` : 'off';
205
220
  }
206
221
  else {
207
- el.setAttribute(attr, `${datum}`);
222
+ el.setAttribute(name, `${datum}`);
208
223
  }
209
- break;
224
+ return;
210
225
  }
211
226
  case 'autofocus': {
212
227
  if (el instanceof HTMLButtonElement ||
@@ -216,84 +231,84 @@ function setAttribute(el, attr, datum) {
216
231
  el.autofocus = datum === '' ? true : !!datum;
217
232
  }
218
233
  else {
219
- el.setAttribute(attr, `${datum}`);
234
+ el.setAttribute(name, `${datum}`);
220
235
  }
221
- break;
236
+ return;
222
237
  }
223
238
  case 'autoplay': {
224
239
  if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
225
240
  el.autoplay = datum === '' ? true : !!datum;
226
241
  }
227
242
  else {
228
- el.setAttribute(attr, `${datum}`);
243
+ el.setAttribute(name, `${datum}`);
229
244
  }
230
- break;
245
+ return;
231
246
  }
232
247
  case 'checked': {
233
248
  if (el instanceof HTMLInputElement) {
234
249
  el.checked = datum === '' ? true : !!datum;
235
250
  }
236
251
  else {
237
- el.setAttribute(attr, `${datum}`);
252
+ el.setAttribute(name, `${datum}`);
238
253
  }
239
- break;
254
+ return;
240
255
  }
241
256
  case 'cols': {
242
257
  if (el instanceof HTMLTextAreaElement) {
243
258
  const cols = toInt(datum);
244
259
  if (Number.isNaN(cols)) {
245
- el.removeAttribute(attr);
260
+ el.removeAttribute(name);
246
261
  }
247
262
  else {
248
263
  el.cols = cols;
249
264
  }
250
265
  }
251
266
  else {
252
- el.setAttribute(attr, `${datum}`);
267
+ el.setAttribute(name, `${datum}`);
253
268
  }
254
- break;
269
+ return;
255
270
  }
256
271
  case 'colspan': {
257
272
  if (el instanceof HTMLTableCellElement) {
258
273
  const colSpan = toInt(datum);
259
274
  if (Number.isNaN(colSpan)) {
260
- el.removeAttribute(attr);
275
+ el.removeAttribute(name);
261
276
  }
262
277
  else {
263
278
  el.colSpan = colSpan;
264
279
  }
265
280
  }
266
281
  else {
267
- el.setAttribute(attr, `${datum}`);
282
+ el.setAttribute(name, `${datum}`);
268
283
  }
269
- break;
284
+ return;
270
285
  }
271
286
  case 'controls': {
272
287
  if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
273
288
  el.controls = datum === '' ? true : !!datum;
274
289
  }
275
290
  else {
276
- el.setAttribute(attr, `${datum}`);
291
+ el.setAttribute(name, `${datum}`);
277
292
  }
278
- break;
293
+ return;
279
294
  }
280
295
  case 'default': {
281
296
  if (el instanceof HTMLTrackElement) {
282
297
  el.default = datum === '' ? true : !!datum;
283
298
  }
284
299
  else {
285
- el.setAttribute(attr, `${datum}`);
300
+ el.setAttribute(name, `${datum}`);
286
301
  }
287
- break;
302
+ return;
288
303
  }
289
304
  case 'defer': {
290
305
  if (el instanceof HTMLScriptElement) {
291
306
  el.defer = !!datum;
292
307
  }
293
308
  else {
294
- el.setAttribute(attr, `${datum}`);
309
+ el.setAttribute(name, `${datum}`);
295
310
  }
296
- break;
311
+ return;
297
312
  }
298
313
  case 'disabled': {
299
314
  if (el instanceof HTMLButtonElement ||
@@ -306,9 +321,9 @@ function setAttribute(el, attr, datum) {
306
321
  el.disabled = datum === '' ? true : !!datum;
307
322
  }
308
323
  else {
309
- el.setAttribute(attr, `${datum}`);
324
+ el.setAttribute(name, `${datum}`);
310
325
  }
311
- break;
326
+ return;
312
327
  }
313
328
  case 'download': {
314
329
  if (el instanceof HTMLAnchorElement) {
@@ -316,61 +331,61 @@ function setAttribute(el, attr, datum) {
316
331
  el.download = `${datum}`;
317
332
  }
318
333
  else {
319
- el.removeAttribute(attr);
334
+ el.removeAttribute(name);
320
335
  }
321
336
  }
322
337
  else {
323
- el.setAttribute(attr, `${datum}`);
338
+ el.setAttribute(name, `${datum}`);
324
339
  }
325
- break;
340
+ return;
326
341
  }
327
342
  case 'high': {
328
343
  if (el instanceof HTMLMeterElement) {
329
344
  const high = toInt(datum);
330
345
  if (Number.isNaN(high)) {
331
- el.removeAttribute(attr);
346
+ el.removeAttribute(name);
332
347
  }
333
348
  else {
334
349
  el.high = high;
335
350
  }
336
351
  }
337
352
  else {
338
- el.setAttribute(attr, `${datum}`);
353
+ el.setAttribute(name, `${datum}`);
339
354
  }
340
- break;
355
+ return;
341
356
  }
342
357
  case 'ismap': {
343
358
  if (el instanceof HTMLImageElement) {
344
359
  el.isMap = datum === '' ? true : !!datum;
345
360
  }
346
361
  else {
347
- el.setAttribute(attr, `${datum}`);
362
+ el.setAttribute(name, `${datum}`);
348
363
  }
349
- break;
364
+ return;
350
365
  }
351
366
  case 'loop': {
352
367
  if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
353
368
  el.loop = datum === '' ? true : !!datum;
354
369
  }
355
370
  else {
356
- el.setAttribute(attr, `${datum}`);
371
+ el.setAttribute(name, `${datum}`);
357
372
  }
358
- break;
373
+ return;
359
374
  }
360
375
  case 'low': {
361
376
  if (el instanceof HTMLMeterElement) {
362
377
  const low = toInt(datum);
363
378
  if (Number.isNaN(low)) {
364
- el.removeAttribute(attr);
379
+ el.removeAttribute(name);
365
380
  }
366
381
  else {
367
382
  el.low = low;
368
383
  }
369
384
  }
370
385
  else {
371
- el.setAttribute(attr, `${datum}`);
386
+ el.setAttribute(name, `${datum}`);
372
387
  }
373
- break;
388
+ return;
374
389
  }
375
390
  case 'max': {
376
391
  if (el instanceof HTMLInputElement ||
@@ -378,88 +393,88 @@ function setAttribute(el, attr, datum) {
378
393
  el instanceof HTMLProgressElement) {
379
394
  const max = toInt(datum);
380
395
  if (Number.isNaN(max)) {
381
- el.removeAttribute(attr);
396
+ el.removeAttribute(name);
382
397
  }
383
398
  else {
384
399
  el.max = max;
385
400
  }
386
401
  }
387
402
  else {
388
- el.setAttribute(attr, `${datum}`);
403
+ el.setAttribute(name, `${datum}`);
389
404
  }
390
- break;
405
+ return;
391
406
  }
392
407
  case 'maxlength': {
393
408
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
394
409
  const maxLength = toInt(datum);
395
410
  if (Number.isNaN(maxLength)) {
396
- el.removeAttribute(attr);
411
+ el.removeAttribute(name);
397
412
  }
398
413
  else {
399
414
  el.maxLength = Math.floor(maxLength);
400
415
  }
401
416
  }
402
417
  else {
403
- el.setAttribute(attr, `${datum}`);
418
+ el.setAttribute(name, `${datum}`);
404
419
  }
405
- break;
420
+ return;
406
421
  }
407
422
  case 'min': {
408
423
  if (el instanceof HTMLInputElement || el instanceof HTMLMeterElement) {
409
424
  const min = toInt(datum);
410
425
  if (Number.isNaN(min)) {
411
- el.removeAttribute(attr);
426
+ el.removeAttribute(name);
412
427
  }
413
428
  else {
414
429
  el.min = min;
415
430
  }
416
431
  }
417
432
  else {
418
- el.setAttribute(attr, `${datum}`);
433
+ el.setAttribute(name, `${datum}`);
419
434
  }
420
- break;
435
+ return;
421
436
  }
422
437
  case 'multiple': {
423
438
  if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement) {
424
439
  el.multiple = datum === '' ? true : !!datum;
425
440
  }
426
441
  else {
427
- el.setAttribute(attr, `${datum}`);
442
+ el.setAttribute(name, `${datum}`);
428
443
  }
429
- break;
444
+ return;
430
445
  }
431
446
  case 'novalidate': {
432
447
  if (el instanceof HTMLFormElement) {
433
448
  el.noValidate = datum === '' ? true : !!datum;
434
449
  }
435
450
  else {
436
- el.setAttribute(attr, `${datum}`);
451
+ el.setAttribute(name, `${datum}`);
452
+ }
453
+ return;
454
+ }
455
+ case 'open': {
456
+ if (el instanceof HTMLDetailsElement) {
457
+ el.open = datum === '' ? true : !!datum;
458
+ }
459
+ else {
460
+ el.setAttribute(name, `${datum}`);
437
461
  }
438
462
  break;
439
463
  }
440
- // ⚰️ Unsupported HTMLDetailsElement yet...
441
- // case 'open': {
442
- // if (el instanceof HTMLDetailsElement) {
443
- // el.open = datum === '' ? true : !!datum;
444
- // } else {
445
- // el.setAttribute(attr, `${datum}`);
446
- // }
447
- // break;
448
- // }
449
464
  case 'optimum': {
450
465
  if (el instanceof HTMLMeterElement) {
451
466
  const optimum = toInt(datum);
452
467
  if (Number.isNaN(optimum)) {
453
- el.removeAttribute(attr);
468
+ el.removeAttribute(name);
454
469
  }
455
470
  else {
456
471
  el.optimum = Math.floor(optimum);
457
472
  }
458
473
  }
459
474
  else {
460
- el.setAttribute(attr, `${datum}`);
475
+ el.setAttribute(name, `${datum}`);
461
476
  }
462
- break;
477
+ return;
463
478
  }
464
479
  case 'preload': {
465
480
  if (el instanceof HTMLAudioElement || el instanceof HTMLVideoElement) {
@@ -476,18 +491,18 @@ function setAttribute(el, attr, datum) {
476
491
  }
477
492
  }
478
493
  else {
479
- el.setAttribute(attr, `${datum}`);
494
+ el.setAttribute(name, `${datum}`);
480
495
  }
481
- break;
496
+ return;
482
497
  }
483
498
  case 'readonly': {
484
499
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
485
500
  el.readOnly = datum === '' ? true : !!datum;
486
501
  }
487
502
  else {
488
- el.setAttribute(attr, `${datum}`);
503
+ el.setAttribute(name, `${datum}`);
489
504
  }
490
- break;
505
+ return;
491
506
  }
492
507
  case 'required': {
493
508
  if (el instanceof HTMLInputElement ||
@@ -496,93 +511,93 @@ function setAttribute(el, attr, datum) {
496
511
  el.required = datum === '' ? true : !!datum;
497
512
  }
498
513
  else {
499
- el.setAttribute(attr, `${datum}`);
514
+ el.setAttribute(name, `${datum}`);
500
515
  }
501
- break;
516
+ return;
502
517
  }
503
518
  case 'reversed': {
504
519
  if (el instanceof HTMLOListElement) {
505
520
  el.reversed = datum === '' ? true : !!datum;
506
521
  }
507
522
  else {
508
- el.setAttribute(attr, `${datum}`);
523
+ el.setAttribute(name, `${datum}`);
509
524
  }
510
- break;
525
+ return;
511
526
  }
512
527
  case 'rows': {
513
528
  if (el instanceof HTMLTextAreaElement) {
514
529
  const rows = toInt(datum);
515
530
  if (Number.isNaN(rows)) {
516
- el.removeAttribute(attr);
531
+ el.removeAttribute(name);
517
532
  }
518
533
  else {
519
534
  el.rows = Math.floor(rows);
520
535
  }
521
536
  }
522
537
  else {
523
- el.setAttribute(attr, `${datum}`);
538
+ el.setAttribute(name, `${datum}`);
524
539
  }
525
- break;
540
+ return;
526
541
  }
527
542
  case 'rowspan': {
528
543
  if (el instanceof HTMLTableCellElement) {
529
544
  const rowSpan = toInt(datum);
530
545
  if (Number.isNaN(rowSpan)) {
531
- el.removeAttribute(attr);
546
+ el.removeAttribute(name);
532
547
  }
533
548
  else {
534
549
  el.rowSpan = Math.floor(rowSpan);
535
550
  }
536
551
  }
537
552
  else {
538
- el.setAttribute(attr, `${datum}`);
553
+ el.setAttribute(name, `${datum}`);
539
554
  }
540
- break;
555
+ return;
541
556
  }
542
557
  case 'size': {
543
558
  if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement) {
544
559
  const size = toInt(datum);
545
560
  if (Number.isNaN(size)) {
546
- el.removeAttribute(attr);
561
+ el.removeAttribute(name);
547
562
  }
548
563
  else {
549
564
  el.size = size;
550
565
  }
551
566
  }
552
567
  else {
553
- el.setAttribute(attr, `${datum}`);
568
+ el.setAttribute(name, `${datum}`);
554
569
  }
555
- break;
570
+ return;
556
571
  }
557
572
  case 'span': {
558
573
  if (el instanceof HTMLTableColElement) {
559
574
  const span = toInt(datum);
560
575
  if (Number.isNaN(span)) {
561
- el.removeAttribute(attr);
576
+ el.removeAttribute(name);
562
577
  }
563
578
  else {
564
579
  el.span = span;
565
580
  }
566
581
  }
567
582
  else {
568
- el.setAttribute(attr, `${datum}`);
583
+ el.setAttribute(name, `${datum}`);
569
584
  }
570
- break;
585
+ return;
571
586
  }
572
587
  case 'start': {
573
588
  if (el instanceof HTMLOListElement) {
574
589
  const start = toInt(datum);
575
590
  if (Number.isNaN(start)) {
576
- el.removeAttribute(attr);
591
+ el.removeAttribute(name);
577
592
  }
578
593
  else {
579
594
  el.start = start;
580
595
  }
581
596
  }
582
597
  else {
583
- el.setAttribute(attr, `${datum}`);
598
+ el.setAttribute(name, `${datum}`);
584
599
  }
585
- break;
600
+ return;
586
601
  }
587
602
  case 'step': {
588
603
  if (el instanceof HTMLInputElement) {
@@ -592,16 +607,16 @@ function setAttribute(el, attr, datum) {
592
607
  }
593
608
  const step = toNum(datum);
594
609
  if (Number.isNaN(step)) {
595
- el.removeAttribute(attr);
610
+ el.removeAttribute(name);
596
611
  }
597
612
  else {
598
613
  el.step = step.toString(10);
599
614
  }
600
615
  }
601
616
  else {
602
- el.setAttribute(attr, `${datum}`);
617
+ el.setAttribute(name, `${datum}`);
603
618
  }
604
- break;
619
+ return;
605
620
  }
606
621
  case 'target': {
607
622
  if (el instanceof HTMLAnchorElement ||
@@ -611,13 +626,13 @@ function setAttribute(el, attr, datum) {
611
626
  el.target = `${datum}`;
612
627
  }
613
628
  else {
614
- el.removeAttribute(attr);
629
+ el.removeAttribute(name);
615
630
  }
616
631
  }
617
632
  else {
618
- el.setAttribute(attr, `${datum}`);
633
+ el.setAttribute(name, `${datum}`);
619
634
  }
620
- break;
635
+ return;
621
636
  }
622
637
  // case 'accesskey':
623
638
  // case 'class':
@@ -669,10 +684,53 @@ function setAttribute(el, attr, datum) {
669
684
  // case 'wrap':
670
685
  // and Data-* attributes
671
686
  default: {
672
- el.setAttribute(attr, `${datum}`);
687
+ el.setAttribute(name, `${datum}`);
673
688
  }
674
689
  }
675
690
  }
691
+ /**
692
+ *
693
+ * @param el
694
+ * @param datum
695
+ * @param asHtml
696
+ * @param xssSanitize
697
+ */
698
+ export function setContent(el, datum, asHtml = true, xssSanitize = true) {
699
+ if (el instanceof HTMLInputElement ||
700
+ el instanceof HTMLSelectElement ||
701
+ el instanceof HTMLTextAreaElement ||
702
+ el instanceof HTMLOutputElement) {
703
+ if (el instanceof HTMLInputElement &&
704
+ (el.type === 'radio' || el.type === 'checkbox')) {
705
+ if (typeof datum === 'boolean') {
706
+ el.checked = datum;
707
+ return;
708
+ }
709
+ el.checked = el.value === `${datum}`;
710
+ return;
711
+ }
712
+ el.value = `${datum}`;
713
+ return;
714
+ }
715
+ if (asHtml) {
716
+ // Sanitize HTML if XSS protection is enabled
717
+ const htmlStr = datum == null ? '' : `${datum}`;
718
+ if (xssSanitize && typeof htmlStr === 'string') {
719
+ // sanitizeHtml will now replace dangerous elements instead of removing them
720
+ el.innerHTML = sanitizeHtml(htmlStr);
721
+ }
722
+ else {
723
+ el.innerHTML = htmlStr;
724
+ }
725
+ return;
726
+ }
727
+ // No need to sanitize textContent (automatically escaped)
728
+ el.textContent = datum == null ? '' : `${datum}`;
729
+ }
730
+ /**
731
+ *
732
+ * @param datum
733
+ */
676
734
  function toNum(datum) {
677
735
  let i;
678
736
  if (typeof datum === 'boolean') {
@@ -686,6 +744,10 @@ function toNum(datum) {
686
744
  }
687
745
  return i;
688
746
  }
747
+ /**
748
+ *
749
+ * @param datum
750
+ */
689
751
  function toInt(datum) {
690
752
  return Math.floor(toNum(datum));
691
753
  }