@burger-editor/frozen-patty 4.0.0-alpha.2 → 4.0.0-alpha.20
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 +544 -21
- package/dist/frozen-patty.d.ts +7 -0
- package/dist/frozen-patty.js +18 -5
- package/dist/{get.d.ts → get-component.d.ts} +1 -1
- package/dist/get-component.js +29 -0
- package/dist/get-values.js +50 -31
- package/dist/parse-fields.d.ts +6 -0
- package/dist/parse-fields.js +38 -0
- package/dist/set-component.d.ts +10 -0
- package/dist/set-component.js +91 -0
- package/dist/set-value.d.ts +4 -2
- package/dist/set-value.js +110 -67
- package/dist/stringify-fields.d.ts +14 -0
- package/dist/stringify-fields.js +26 -0
- package/dist/types.d.ts +7 -0
- package/dist/utils.d.ts +72 -5
- package/dist/utils.js +246 -6
- package/package.json +7 -5
- package/LICENSE +0 -21
- package/dist/field-name-parser.d.ts +0 -8
- package/dist/field-name-parser.js +0 -18
- package/dist/get.js +0 -19
- package/dist/set.d.ts +0 -9
- package/dist/set.js +0 -60
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type { FrozenPattyData, PrimitiveDatum } from './types.js';
|
|
1
|
+
import type { FieldDefinition, FrozenPattyData, FrozenPattyFlattenData, PrimitiveDatum } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* List of dangerous HTML elements to block for XSS protection
|
|
4
|
+
*/
|
|
5
|
+
export declare const DANGEROUS_ELEMENTS: string[];
|
|
2
6
|
/**
|
|
3
7
|
*
|
|
4
8
|
* @param kvs
|
|
@@ -8,11 +12,74 @@ export declare function arrayToHash(kvs: [keyof FrozenPattyData, PrimitiveDatum,
|
|
|
8
12
|
};
|
|
9
13
|
/**
|
|
10
14
|
*
|
|
11
|
-
* @param
|
|
15
|
+
* @param el
|
|
16
|
+
* @param nodeName
|
|
17
|
+
* @param attr
|
|
18
|
+
* @param xssSanitize
|
|
19
|
+
*/
|
|
20
|
+
export declare function replaceNode(el: Element, nodeName: string, attr: string, xssSanitize?: boolean): Element;
|
|
21
|
+
/**
|
|
22
|
+
* Removes dangerous script elements from HTML string
|
|
23
|
+
* @param html HTML string
|
|
24
|
+
* @returns Sanitized HTML
|
|
25
|
+
*/
|
|
26
|
+
export declare function sanitizeHtml(html: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Sanitizes attribute value
|
|
29
|
+
* @param name Attribute name
|
|
30
|
+
* @param value Attribute value
|
|
31
|
+
* @returns Safe attribute value, or null if dangerous
|
|
32
|
+
*/
|
|
33
|
+
export declare function sanitizeAttributeValue<T extends PrimitiveDatum>(name: string, value: T): T | null;
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @param data
|
|
37
|
+
* @param definedFields
|
|
38
|
+
*/
|
|
39
|
+
export declare function maxLengthOf(data: FrozenPattyData, definedFields: string[]): number;
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param el
|
|
43
|
+
* @param attr
|
|
44
|
+
*/
|
|
45
|
+
export declare function definedFields(el: Element, attr: string): string[];
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param el
|
|
49
|
+
* @param attr
|
|
50
|
+
*/
|
|
51
|
+
export declare function getFields(el: Element, attr: string): readonly FieldDefinition[];
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @param el
|
|
55
|
+
* @param attr
|
|
56
|
+
* @param dataKeyName
|
|
57
|
+
*/
|
|
58
|
+
export declare function hasField(el: Element, attr: string, dataKeyName: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param fields
|
|
62
|
+
* @param oldPropName
|
|
63
|
+
* @param newPropName
|
|
64
|
+
*/
|
|
65
|
+
export declare function replaceProp(fields: readonly FieldDefinition[], oldPropName: string, newPropName: string): FieldDefinition[];
|
|
66
|
+
/**
|
|
67
|
+
*
|
|
68
|
+
* @param fields
|
|
69
|
+
* @param propName
|
|
70
|
+
*/
|
|
71
|
+
export declare function removeProp(fields: readonly FieldDefinition[], propName: string): FieldDefinition[];
|
|
72
|
+
/**
|
|
73
|
+
* - 値が配列の場合は、index に対応する値を返す。
|
|
74
|
+
* - 値が配列でない場合は、そのまま返す。
|
|
75
|
+
* - ⚠️ 値が配列で、index に対応する値が undefined の場合は、配列の最初の値を返す。
|
|
76
|
+
* @param data
|
|
77
|
+
* @param index
|
|
12
78
|
*/
|
|
13
|
-
export declare function
|
|
79
|
+
export declare function flattenData(data: FrozenPattyData, index: number): FrozenPattyFlattenData;
|
|
14
80
|
/**
|
|
15
81
|
*
|
|
16
|
-
* @param
|
|
82
|
+
* @param el
|
|
83
|
+
* @param name
|
|
17
84
|
*/
|
|
18
|
-
export declare function
|
|
85
|
+
export declare function propInElement(el: Element, name: string): boolean;
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import { parseFields } from './parse-fields.js';
|
|
2
|
+
/**
|
|
3
|
+
* List of dangerous HTML elements to block for XSS protection
|
|
4
|
+
*/
|
|
5
|
+
export const DANGEROUS_ELEMENTS = [
|
|
6
|
+
'script',
|
|
7
|
+
'style',
|
|
8
|
+
'template',
|
|
9
|
+
'object',
|
|
10
|
+
'embed',
|
|
11
|
+
'iframe',
|
|
12
|
+
'frame',
|
|
13
|
+
'frameset',
|
|
14
|
+
'applet',
|
|
15
|
+
];
|
|
1
16
|
/**
|
|
2
17
|
*
|
|
3
18
|
* @param kvs
|
|
@@ -23,15 +38,240 @@ export function arrayToHash(kvs) {
|
|
|
23
38
|
}
|
|
24
39
|
/**
|
|
25
40
|
*
|
|
26
|
-
* @param
|
|
41
|
+
* @param el
|
|
42
|
+
* @param nodeName
|
|
43
|
+
* @param attr
|
|
44
|
+
* @param xssSanitize
|
|
45
|
+
*/
|
|
46
|
+
export function replaceNode(el, nodeName, attr, xssSanitize = true) {
|
|
47
|
+
nodeName = nodeName.toLowerCase();
|
|
48
|
+
const currentName = el.localName ?? el.nodeName.toLowerCase();
|
|
49
|
+
if (currentName === nodeName) {
|
|
50
|
+
return el;
|
|
51
|
+
}
|
|
52
|
+
// Check element name if XSS protection is enabled
|
|
53
|
+
if (xssSanitize && // Disallow dangerous elements
|
|
54
|
+
DANGEROUS_ELEMENTS.includes(nodeName)) {
|
|
55
|
+
return el;
|
|
56
|
+
}
|
|
57
|
+
const node = (el.ownerDocument ?? document).createElement(nodeName);
|
|
58
|
+
for (const child of el.childNodes) {
|
|
59
|
+
node.append(child.cloneNode(true));
|
|
60
|
+
}
|
|
61
|
+
const fields = getFields(el, attr);
|
|
62
|
+
const attrs = new Set(fields.map((field) => field.propName));
|
|
63
|
+
for (const { name, value } of el.attributes) {
|
|
64
|
+
if (attrs.has(name)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Check attribute value if XSS protection is enabled
|
|
68
|
+
if (xssSanitize) {
|
|
69
|
+
const sanitizedValue = sanitizeAttributeValue(name, value);
|
|
70
|
+
if (sanitizedValue !== null) {
|
|
71
|
+
node.setAttribute(name, sanitizedValue);
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
node.setAttribute(name, value);
|
|
76
|
+
}
|
|
77
|
+
el.replaceWith(node);
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Removes dangerous script elements from HTML string
|
|
82
|
+
* @param html HTML string
|
|
83
|
+
* @returns Sanitized HTML
|
|
84
|
+
*/
|
|
85
|
+
export function sanitizeHtml(html) {
|
|
86
|
+
if (typeof html !== 'string') {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
// Simple approach to handle script tags content
|
|
90
|
+
// First remove script tags but keep their content
|
|
91
|
+
let sanitized = html;
|
|
92
|
+
// For each dangerous element, replace tag but keep content using regex
|
|
93
|
+
// Example: <script>alert("XSS")</script> -> alert("XSS")
|
|
94
|
+
for (const tagName of DANGEROUS_ELEMENTS) {
|
|
95
|
+
const regex = new RegExp(`<${tagName}[^>]*>(.*?)<\\/${tagName}>`, 'gis');
|
|
96
|
+
sanitized = sanitized.replace(regex, '$1');
|
|
97
|
+
}
|
|
98
|
+
// Parse HTML safely using DOM parser for remaining sanitization
|
|
99
|
+
const doc = new DOMParser().parseFromString(sanitized, 'text/html');
|
|
100
|
+
const body = doc.body;
|
|
101
|
+
// Remove event handler attributes from all elements
|
|
102
|
+
sanitizeElementAndChildren(body);
|
|
103
|
+
return body.innerHTML;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Sanitizes an element and all its child elements
|
|
107
|
+
* @param element Target element
|
|
108
|
+
*/
|
|
109
|
+
function sanitizeElementAndChildren(element) {
|
|
110
|
+
// Remove dangerous attributes from current element
|
|
111
|
+
sanitizeAttributes(element);
|
|
112
|
+
// Process child elements recursively
|
|
113
|
+
const children = element.children;
|
|
114
|
+
for (const child of children) {
|
|
115
|
+
sanitizeElementAndChildren(child);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Sanitizes attribute value
|
|
120
|
+
* @param name Attribute name
|
|
121
|
+
* @param value Attribute value
|
|
122
|
+
* @returns Safe attribute value, or null if dangerous
|
|
123
|
+
*/
|
|
124
|
+
export function sanitizeAttributeValue(name, value) {
|
|
125
|
+
if (typeof value !== 'string') {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
name = name.toLowerCase();
|
|
129
|
+
// Disallow event handler attributes
|
|
130
|
+
if (name.startsWith('on')) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
// Check for dangerous protocol in link attributes
|
|
134
|
+
if (name === 'href' ||
|
|
135
|
+
name === 'src' ||
|
|
136
|
+
name === 'action' ||
|
|
137
|
+
name === 'formaction' ||
|
|
138
|
+
name === 'xlink:href') {
|
|
139
|
+
const trimmedValue = value.trim().toLowerCase();
|
|
140
|
+
if (trimmedValue.startsWith('javascript:') ||
|
|
141
|
+
trimmedValue.startsWith('data:') ||
|
|
142
|
+
trimmedValue.startsWith('vbscript:')) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Other dangerous attributes
|
|
147
|
+
if (['manifest'].includes(name)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Removes dangerous attributes from an element
|
|
154
|
+
* @param element Target element
|
|
155
|
+
*/
|
|
156
|
+
function sanitizeAttributes(element) {
|
|
157
|
+
// Collect list of dangerous attributes
|
|
158
|
+
const dangerousAttrs = [...element.attributes]
|
|
159
|
+
.filter((attr) => sanitizeAttributeValue(attr.name, attr.value) === null)
|
|
160
|
+
.map((attr) => attr.name);
|
|
161
|
+
// Remove dangerous attributes
|
|
162
|
+
for (const attrName of dangerousAttrs) {
|
|
163
|
+
element.removeAttribute(attrName);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
*
|
|
168
|
+
* @param data
|
|
169
|
+
* @param definedFields
|
|
170
|
+
*/
|
|
171
|
+
export function maxLengthOf(data, definedFields) {
|
|
172
|
+
let maxLength = 0;
|
|
173
|
+
for (const key in data) {
|
|
174
|
+
if (definedFields.includes(key) && Array.isArray(data[key])) {
|
|
175
|
+
maxLength = Math.max(maxLength, data[key].length);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return maxLength;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
*
|
|
182
|
+
* @param el
|
|
183
|
+
* @param attr
|
|
184
|
+
*/
|
|
185
|
+
export function definedFields(el, attr) {
|
|
186
|
+
const fieldDefinitions = getFields(el, attr);
|
|
187
|
+
return fieldDefinitions.map((field) => field.fieldName);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @param el
|
|
192
|
+
* @param attr
|
|
193
|
+
*/
|
|
194
|
+
export function getFields(el, attr) {
|
|
195
|
+
const fields = el.getAttribute(`data-${attr}`);
|
|
196
|
+
if (!fields) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
return parseFields(fields);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
*
|
|
203
|
+
* @param el
|
|
204
|
+
* @param attr
|
|
205
|
+
* @param dataKeyName
|
|
206
|
+
*/
|
|
207
|
+
export function hasField(el, attr, dataKeyName) {
|
|
208
|
+
const fieldDefinitions = getFields(el, attr);
|
|
209
|
+
return fieldDefinitions.some((field) => field.fieldName === dataKeyName);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
*
|
|
213
|
+
* @param fields
|
|
214
|
+
* @param oldPropName
|
|
215
|
+
* @param newPropName
|
|
27
216
|
*/
|
|
28
|
-
export function
|
|
29
|
-
return
|
|
217
|
+
export function replaceProp(fields, oldPropName, newPropName) {
|
|
218
|
+
return fields.map((field) => {
|
|
219
|
+
if (field.propName === oldPropName) {
|
|
220
|
+
return {
|
|
221
|
+
...field,
|
|
222
|
+
propName: newPropName,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return field;
|
|
226
|
+
});
|
|
30
227
|
}
|
|
31
228
|
/**
|
|
32
229
|
*
|
|
33
|
-
* @param
|
|
230
|
+
* @param fields
|
|
231
|
+
* @param propName
|
|
34
232
|
*/
|
|
35
|
-
export function
|
|
36
|
-
return
|
|
233
|
+
export function removeProp(fields, propName) {
|
|
234
|
+
return fields.filter((field) => field.propName !== propName);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* - 値が配列の場合は、index に対応する値を返す。
|
|
238
|
+
* - 値が配列でない場合は、そのまま返す。
|
|
239
|
+
* - ⚠️ 値が配列で、index に対応する値が undefined の場合は、配列の最初の値を返す。
|
|
240
|
+
* @param data
|
|
241
|
+
* @param index
|
|
242
|
+
*/
|
|
243
|
+
export function flattenData(data, index) {
|
|
244
|
+
const result = {};
|
|
245
|
+
for (const key in data) {
|
|
246
|
+
result[key] = Array.isArray(data[key])
|
|
247
|
+
? data[key][index] === undefined
|
|
248
|
+
? data[key][0]
|
|
249
|
+
: data[key][index]
|
|
250
|
+
: data[key];
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 特定の要素で、`propName in element`で判定できない属性名
|
|
256
|
+
*/
|
|
257
|
+
const specificProp = {
|
|
258
|
+
/*
|
|
259
|
+
* JSDOMの未実装対策
|
|
260
|
+
* @see https://github.com/jsdom/jsdom/blob/main/lib/jsdom/living/nodes/HTMLSourceElement.webidl
|
|
261
|
+
* Living Standardでは実装済み
|
|
262
|
+
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#htmlsourceelement
|
|
263
|
+
*/
|
|
264
|
+
source: ['width', 'height'],
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
*
|
|
268
|
+
* @param el
|
|
269
|
+
* @param name
|
|
270
|
+
*/
|
|
271
|
+
export function propInElement(el, name) {
|
|
272
|
+
const has = name in el;
|
|
273
|
+
if (has) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
return specificProp[el.localName]?.includes(name) ?? false;
|
|
37
277
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burger-editor/frozen-patty",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
4
|
-
"description": "Pure HTML to JSON converter that not use template engine.",
|
|
3
|
+
"version": "4.0.0-alpha.20",
|
|
4
|
+
"description": "Pure HTML to JSON converter that does not use a template engine.",
|
|
5
5
|
"author": "D-ZERO",
|
|
6
|
-
"license": "MIT",
|
|
6
|
+
"license": "(MIT OR Apache-2.0)",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/d-zero-dev/BurgerEditor.git"
|
|
9
9
|
},
|
|
10
|
-
"private": false,
|
|
11
10
|
"publishConfig": {
|
|
12
11
|
"access": "public"
|
|
13
12
|
},
|
|
@@ -41,5 +40,8 @@
|
|
|
41
40
|
"dev": "tsc --watch --project tsconfig.build.json",
|
|
42
41
|
"clean": "tsc --build --clean"
|
|
43
42
|
},
|
|
44
|
-
"
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@burger-editor/utils": "4.0.0-alpha.20"
|
|
45
|
+
},
|
|
46
|
+
"gitHead": "e275ac1661550a98998549b2265ae5600fb95600"
|
|
45
47
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2017-2024 D-ZERO Co., Ltd.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { camelCase } from './utils.js';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
* @param field
|
|
5
|
-
*/
|
|
6
|
-
export function fieldNameParser(field) {
|
|
7
|
-
field = field.trim().toLowerCase();
|
|
8
|
-
if (!field) {
|
|
9
|
-
throw new Error('Field name is empty.');
|
|
10
|
-
}
|
|
11
|
-
let [fieldName, propName] = field.split(':');
|
|
12
|
-
propName = propName?.trim() || '';
|
|
13
|
-
fieldName = fieldName?.trim() || propName || field;
|
|
14
|
-
return {
|
|
15
|
-
fieldName: camelCase(fieldName),
|
|
16
|
-
propName,
|
|
17
|
-
};
|
|
18
|
-
}
|
package/dist/get.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { getValues } from './get-values.js';
|
|
2
|
-
import { arrayToHash } from './utils.js';
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* @param el
|
|
6
|
-
* @param attr
|
|
7
|
-
* @param typeConvert
|
|
8
|
-
* @param filter
|
|
9
|
-
*/
|
|
10
|
-
export default function (el, attr, typeConvert, filter) {
|
|
11
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
12
|
-
const filedElements = Array.from(el.querySelectorAll(`[data-${attr}]`));
|
|
13
|
-
let values = [];
|
|
14
|
-
for (const _el of filedElements) {
|
|
15
|
-
values = [...values, ...getValues(_el, typeConvert, attr, filter)];
|
|
16
|
-
}
|
|
17
|
-
const result = arrayToHash(values);
|
|
18
|
-
return result;
|
|
19
|
-
}
|
package/dist/set.d.ts
DELETED
package/dist/set.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { setValue } from './set-value.js';
|
|
2
|
-
import { kebabCase } from './utils.js';
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* @param el
|
|
6
|
-
* @param data
|
|
7
|
-
* @param attr
|
|
8
|
-
* @param filter
|
|
9
|
-
*/
|
|
10
|
-
export default function (el, data, attr, filter) {
|
|
11
|
-
el = el.cloneNode(true);
|
|
12
|
-
for (const dataKeyName in data) {
|
|
13
|
-
if (dataKeyName in data) {
|
|
14
|
-
const datum = data[dataKeyName];
|
|
15
|
-
const selector = `[data-${attr}*="${kebabCase(dataKeyName)}"]`;
|
|
16
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
17
|
-
const targetList = Array.from(el.querySelectorAll(selector));
|
|
18
|
-
if (Array.isArray(datum)) {
|
|
19
|
-
const targetEl = targetList[0];
|
|
20
|
-
if (!targetEl) {
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
const listRoot = targetEl.closest(`[data-${attr}-list]`);
|
|
24
|
-
if (!listRoot || (listRoot && listRoot.children.length === 0)) {
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
const listItem = listRoot.children[0]?.cloneNode(true);
|
|
28
|
-
while (listItem && datum.length > listRoot.children.length) {
|
|
29
|
-
listRoot.append(listItem.cloneNode(true));
|
|
30
|
-
}
|
|
31
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
32
|
-
const newChildren = Array.from(listRoot.querySelectorAll(selector));
|
|
33
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
34
|
-
const oldChildList = Array.from(listRoot.children);
|
|
35
|
-
let deleteNodeList = [];
|
|
36
|
-
for (const [i, child] of [...newChildren].entries()) {
|
|
37
|
-
if (datum[i] == null) {
|
|
38
|
-
setValue(child, dataKeyName, '', attr, filter);
|
|
39
|
-
const oldChild = oldChildList[i];
|
|
40
|
-
if (oldChild) {
|
|
41
|
-
deleteNodeList.push(oldChild);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
setValue(child, dataKeyName, datum[i], attr, filter);
|
|
46
|
-
deleteNodeList = [];
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
for (const node of deleteNodeList)
|
|
50
|
-
node.remove();
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
for (const [, targetEl] of [...targetList].entries()) {
|
|
54
|
-
setValue(targetEl, dataKeyName, datum, attr, filter);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return el;
|
|
60
|
-
}
|