@dodlhuat/basix 1.2.2 → 1.2.4
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 +45 -1
- package/css/lightbox.scss +272 -0
- package/css/style.css +263 -3
- package/css/style.css.map +1 -1
- package/css/style.scss +1 -0
- package/js/bottom-sheet.js +4 -3
- package/js/bottom-sheet.ts +5 -3
- package/js/calendar.js +9 -5
- package/js/calendar.ts +7 -2
- package/js/carousel.js +15 -11
- package/js/carousel.ts +16 -11
- package/js/chart.js +4 -4
- package/js/chart.ts +5 -3
- package/js/datepicker.js +11 -3
- package/js/datepicker.ts +13 -3
- package/js/docs-nav.js +1 -0
- package/js/editor.js +28 -20
- package/js/editor.ts +28 -20
- package/js/file-uploader.js +6 -10
- package/js/file-uploader.ts +7 -11
- package/js/flyout-menu.js +8 -2
- package/js/flyout-menu.ts +7 -2
- package/js/gallery.js +6 -13
- package/js/gallery.ts +8 -16
- package/js/group-picker.js +10 -7
- package/js/group-picker.ts +11 -7
- package/js/lightbox.js +277 -0
- package/js/lightbox.ts +331 -0
- package/js/modal.js +5 -4
- package/js/modal.ts +6 -4
- package/js/popover.js +4 -2
- package/js/popover.ts +4 -2
- package/js/push-menu.js +3 -2
- package/js/push-menu.ts +4 -2
- package/js/scrollbar.js +31 -23
- package/js/scrollbar.ts +36 -26
- package/js/select.js +23 -9
- package/js/select.ts +29 -11
- package/js/stepper.js +5 -1
- package/js/stepper.ts +6 -1
- package/js/table.js +8 -3
- package/js/table.ts +9 -3
- package/js/timepicker.js +32 -21
- package/js/timepicker.ts +29 -21
- package/js/toast.js +3 -7
- package/js/toast.ts +4 -8
- package/js/tooltip.js +13 -4
- package/js/tooltip.ts +16 -4
- package/js/tree.js +4 -0
- package/js/tree.ts +5 -0
- package/js/utils.js +29 -1
- package/js/utils.ts +36 -1
- package/js/virtual-dropdown.js +4 -8
- package/js/virtual-dropdown.ts +5 -9
- package/package.json +1 -1
package/js/timepicker.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
class TimeSpanPicker {
|
|
2
|
-
constructor(
|
|
3
|
-
|
|
2
|
+
constructor(elementOrSelector, options) {
|
|
3
|
+
this.handleStartChange = () => { this.handleChange(); };
|
|
4
|
+
this.handleEndChange = () => { this.handleChange(); };
|
|
5
|
+
const element = typeof elementOrSelector === 'string'
|
|
6
|
+
? (elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
|
|
7
|
+
? document.querySelector(elementOrSelector)
|
|
8
|
+
: document.getElementById(elementOrSelector))
|
|
9
|
+
: elementOrSelector;
|
|
4
10
|
if (!element) {
|
|
5
|
-
throw new Error(`
|
|
11
|
+
throw new Error(`TimeSpanPicker: Element not found for "${elementOrSelector}"`);
|
|
6
12
|
}
|
|
7
13
|
this.container = element;
|
|
8
14
|
this.onChange = options?.onChange;
|
|
15
|
+
this.uid = `tsp-${Math.random().toString(36).slice(2, 9)}`;
|
|
9
16
|
this.render();
|
|
10
17
|
this.startTimeInput = this.queryInput('.timespan-start');
|
|
11
18
|
this.endTimeInput = this.queryInput('.timespan-end');
|
|
@@ -15,6 +22,18 @@ class TimeSpanPicker {
|
|
|
15
22
|
if (options?.defaultEnd) {
|
|
16
23
|
this.endTimeInput.value = options.defaultEnd;
|
|
17
24
|
}
|
|
25
|
+
if (options?.fromString) {
|
|
26
|
+
this.fromString = options.fromString;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.fromString = 'From';
|
|
30
|
+
}
|
|
31
|
+
if (options?.toString) {
|
|
32
|
+
this.toString = options.toString;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
this.toString = 'To';
|
|
36
|
+
}
|
|
18
37
|
this.attachEventListeners();
|
|
19
38
|
// Render initial state if defaults provided
|
|
20
39
|
if (options?.defaultStart || options?.defaultEnd) {
|
|
@@ -29,15 +48,13 @@ class TimeSpanPicker {
|
|
|
29
48
|
return input;
|
|
30
49
|
}
|
|
31
50
|
render() {
|
|
51
|
+
const startId = `${this.uid}-start`;
|
|
52
|
+
const endId = `${this.uid}-end`;
|
|
32
53
|
this.container.innerHTML = `
|
|
33
54
|
<div class="timespan-picker">
|
|
34
55
|
<div class="timespan-field timespan-field-start">
|
|
35
|
-
<label for="
|
|
36
|
-
<input
|
|
37
|
-
type="time"
|
|
38
|
-
class="timespan-start"
|
|
39
|
-
id="timespan-start"
|
|
40
|
-
/>
|
|
56
|
+
<label for="${startId}">${this.fromString}</label>
|
|
57
|
+
<input type="time" class="timespan-start" id="${startId}"/>
|
|
41
58
|
</div>
|
|
42
59
|
|
|
43
60
|
<div class="timespan-center">
|
|
@@ -46,12 +63,8 @@ class TimeSpanPicker {
|
|
|
46
63
|
</div>
|
|
47
64
|
|
|
48
65
|
<div class="timespan-field timespan-field-end">
|
|
49
|
-
<label for="
|
|
50
|
-
<input
|
|
51
|
-
type="time"
|
|
52
|
-
class="timespan-end"
|
|
53
|
-
id="timespan-end"
|
|
54
|
-
/>
|
|
66
|
+
<label for="${endId}">${this.toString}</label>
|
|
67
|
+
<input type="time" class="timespan-end" id="${endId}"/>
|
|
55
68
|
</div>
|
|
56
69
|
</div>
|
|
57
70
|
<div class="timespan-bar" aria-hidden="true">
|
|
@@ -60,8 +73,8 @@ class TimeSpanPicker {
|
|
|
60
73
|
`;
|
|
61
74
|
}
|
|
62
75
|
attachEventListeners() {
|
|
63
|
-
this.startTimeInput.addEventListener('change',
|
|
64
|
-
this.endTimeInput.addEventListener('change',
|
|
76
|
+
this.startTimeInput.addEventListener('change', this.handleStartChange);
|
|
77
|
+
this.endTimeInput.addEventListener('change', this.handleEndChange);
|
|
65
78
|
}
|
|
66
79
|
toMinutes(time) {
|
|
67
80
|
const [h, m] = time.split(':').map(Number);
|
|
@@ -148,10 +161,8 @@ class TimeSpanPicker {
|
|
|
148
161
|
return !!(start && end && start < end);
|
|
149
162
|
}
|
|
150
163
|
destroy() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.startTimeInput.removeEventListener('change', startHandler);
|
|
154
|
-
this.endTimeInput.removeEventListener('change', endHandler);
|
|
164
|
+
this.startTimeInput.removeEventListener('change', this.handleStartChange);
|
|
165
|
+
this.endTimeInput.removeEventListener('change', this.handleEndChange);
|
|
155
166
|
}
|
|
156
167
|
}
|
|
157
168
|
export { TimeSpanPicker };
|
package/js/timepicker.ts
CHANGED
|
@@ -7,6 +7,8 @@ interface TimeSpanPickerOptions {
|
|
|
7
7
|
onChange?: (start: string, end: string) => void;
|
|
8
8
|
defaultStart?: string;
|
|
9
9
|
defaultEnd?: string;
|
|
10
|
+
fromString?: string;
|
|
11
|
+
toString?: string;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
class TimeSpanPicker {
|
|
@@ -14,15 +16,26 @@ class TimeSpanPicker {
|
|
|
14
16
|
private startTimeInput: HTMLInputElement;
|
|
15
17
|
private endTimeInput: HTMLInputElement;
|
|
16
18
|
private onChange?: (start: string, end: string) => void;
|
|
19
|
+
private readonly uid: string;
|
|
20
|
+
private fromString: string;
|
|
21
|
+
private toString: string;
|
|
22
|
+
|
|
23
|
+
constructor(elementOrSelector: string | HTMLElement, options?: TimeSpanPickerOptions) {
|
|
24
|
+
const element = typeof elementOrSelector === 'string'
|
|
25
|
+
? (elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
|
|
26
|
+
? document.querySelector<HTMLElement>(elementOrSelector)
|
|
27
|
+
: document.getElementById(elementOrSelector))
|
|
28
|
+
: elementOrSelector;
|
|
17
29
|
|
|
18
|
-
constructor(containerId: string, options?: TimeSpanPickerOptions) {
|
|
19
|
-
const element = document.getElementById(containerId);
|
|
20
30
|
if (!element) {
|
|
21
|
-
throw new Error(`
|
|
31
|
+
throw new Error(`TimeSpanPicker: Element not found for "${elementOrSelector}"`);
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
this.container = element;
|
|
25
35
|
this.onChange = options?.onChange;
|
|
36
|
+
this.uid = `tsp-${Math.random().toString(36).slice(2, 9)}`;
|
|
37
|
+
this.fromString = options?.fromString ?? 'From';
|
|
38
|
+
this.toString = options?.toString ?? 'To';
|
|
26
39
|
|
|
27
40
|
this.render();
|
|
28
41
|
|
|
@@ -53,15 +66,13 @@ class TimeSpanPicker {
|
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
private render(): void {
|
|
69
|
+
const startId = `${this.uid}-start`;
|
|
70
|
+
const endId = `${this.uid}-end`;
|
|
56
71
|
this.container.innerHTML = `
|
|
57
72
|
<div class="timespan-picker">
|
|
58
73
|
<div class="timespan-field timespan-field-start">
|
|
59
|
-
<label for="
|
|
60
|
-
<input
|
|
61
|
-
type="time"
|
|
62
|
-
class="timespan-start"
|
|
63
|
-
id="timespan-start"
|
|
64
|
-
/>
|
|
74
|
+
<label for="${startId}">${this.fromString}</label>
|
|
75
|
+
<input type="time" class="timespan-start" id="${startId}"/>
|
|
65
76
|
</div>
|
|
66
77
|
|
|
67
78
|
<div class="timespan-center">
|
|
@@ -70,12 +81,8 @@ class TimeSpanPicker {
|
|
|
70
81
|
</div>
|
|
71
82
|
|
|
72
83
|
<div class="timespan-field timespan-field-end">
|
|
73
|
-
<label for="
|
|
74
|
-
<input
|
|
75
|
-
type="time"
|
|
76
|
-
class="timespan-end"
|
|
77
|
-
id="timespan-end"
|
|
78
|
-
/>
|
|
84
|
+
<label for="${endId}">${this.toString}</label>
|
|
85
|
+
<input type="time" class="timespan-end" id="${endId}"/>
|
|
79
86
|
</div>
|
|
80
87
|
</div>
|
|
81
88
|
<div class="timespan-bar" aria-hidden="true">
|
|
@@ -84,9 +91,12 @@ class TimeSpanPicker {
|
|
|
84
91
|
`;
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
private readonly handleStartChange = (): void => { this.handleChange(); };
|
|
95
|
+
private readonly handleEndChange = (): void => { this.handleChange(); };
|
|
96
|
+
|
|
87
97
|
private attachEventListeners(): void {
|
|
88
|
-
this.startTimeInput.addEventListener('change',
|
|
89
|
-
this.endTimeInput.addEventListener('change',
|
|
98
|
+
this.startTimeInput.addEventListener('change', this.handleStartChange);
|
|
99
|
+
this.endTimeInput.addEventListener('change', this.handleEndChange);
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
private toMinutes(time: string): number {
|
|
@@ -183,10 +193,8 @@ class TimeSpanPicker {
|
|
|
183
193
|
}
|
|
184
194
|
|
|
185
195
|
public destroy(): void {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.startTimeInput.removeEventListener('change', startHandler);
|
|
189
|
-
this.endTimeInput.removeEventListener('change', endHandler);
|
|
196
|
+
this.startTimeInput.removeEventListener('change', this.handleStartChange);
|
|
197
|
+
this.endTimeInput.removeEventListener('change', this.handleEndChange);
|
|
190
198
|
}
|
|
191
199
|
}
|
|
192
200
|
|
package/js/toast.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escapeHtml } from './utils.js';
|
|
1
2
|
class Toast {
|
|
2
3
|
constructor(contentOrOptions, header = '', type, closeable = true) {
|
|
3
4
|
this.closureIcon = '<div class="icon icon-close close"></div>';
|
|
@@ -79,15 +80,10 @@ class Toast {
|
|
|
79
80
|
parts.push(this.closureIcon);
|
|
80
81
|
}
|
|
81
82
|
if (this.header) {
|
|
82
|
-
parts.push(`<div class="header">${
|
|
83
|
+
parts.push(`<div class="header">${escapeHtml(this.header)}</div>`);
|
|
83
84
|
}
|
|
84
|
-
parts.push(`<div class="content">${
|
|
85
|
+
parts.push(`<div class="content">${escapeHtml(this.content)}</div>`);
|
|
85
86
|
return parts.join('');
|
|
86
87
|
}
|
|
87
|
-
escapeHtml(text) {
|
|
88
|
-
const div = document.createElement('div');
|
|
89
|
-
div.textContent = text;
|
|
90
|
-
return div.innerHTML;
|
|
91
|
-
}
|
|
92
88
|
}
|
|
93
89
|
export { Toast };
|
package/js/toast.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { escapeHtml } from './utils.js';
|
|
2
|
+
|
|
1
3
|
type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
2
4
|
|
|
3
5
|
interface ToastOptions {
|
|
@@ -119,19 +121,13 @@ class Toast {
|
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
if (this.header) {
|
|
122
|
-
parts.push(`<div class="header">${
|
|
124
|
+
parts.push(`<div class="header">${escapeHtml(this.header)}</div>`);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
parts.push(`<div class="content">${
|
|
127
|
+
parts.push(`<div class="content">${escapeHtml(this.content)}</div>`);
|
|
126
128
|
|
|
127
129
|
return parts.join('');
|
|
128
130
|
}
|
|
129
|
-
|
|
130
|
-
private escapeHtml(text: string): string {
|
|
131
|
-
const div = document.createElement('div');
|
|
132
|
-
div.textContent = text;
|
|
133
|
-
return div.innerHTML;
|
|
134
|
-
}
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
export { Toast };
|
package/js/tooltip.js
CHANGED
|
@@ -23,7 +23,8 @@ class Tooltip {
|
|
|
23
23
|
position: options.position ?? 'auto',
|
|
24
24
|
offset: options.offset ?? 8,
|
|
25
25
|
delay: options.delay ?? 0,
|
|
26
|
-
className: options.className ?? ''
|
|
26
|
+
className: options.className ?? '',
|
|
27
|
+
isHtml: options.isHtml ?? false,
|
|
27
28
|
};
|
|
28
29
|
this.attachEvents();
|
|
29
30
|
}
|
|
@@ -34,7 +35,7 @@ class Tooltip {
|
|
|
34
35
|
const position = trigger.getAttribute('data-tooltip-position') ?? 'auto';
|
|
35
36
|
const className = trigger.getAttribute('data-tooltip-class') ?? '';
|
|
36
37
|
if (content) {
|
|
37
|
-
new Tooltip(trigger, content, { position, className });
|
|
38
|
+
new Tooltip(trigger, content, { position, className, isHtml: false });
|
|
38
39
|
}
|
|
39
40
|
});
|
|
40
41
|
// Also support content from separate elements
|
|
@@ -47,7 +48,7 @@ class Tooltip {
|
|
|
47
48
|
const contentElement = document.getElementById(contentId);
|
|
48
49
|
if (contentElement) {
|
|
49
50
|
const content = contentElement.innerHTML;
|
|
50
|
-
new Tooltip(trigger, content, { position, className });
|
|
51
|
+
new Tooltip(trigger, content, { position, className, isHtml: true });
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
});
|
|
@@ -94,7 +95,15 @@ class Tooltip {
|
|
|
94
95
|
tooltip.className = 'tooltip';
|
|
95
96
|
tooltip.id = `tooltip-${++Tooltip.idCounter}`;
|
|
96
97
|
tooltip.setAttribute('role', 'tooltip');
|
|
97
|
-
|
|
98
|
+
const tooltipContent = document.createElement('div');
|
|
99
|
+
tooltipContent.className = 'tooltip-content';
|
|
100
|
+
if (this.options.isHtml) {
|
|
101
|
+
tooltipContent.innerHTML = this.content;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
tooltipContent.textContent = this.content;
|
|
105
|
+
}
|
|
106
|
+
tooltip.appendChild(tooltipContent);
|
|
98
107
|
if (this.options.className) {
|
|
99
108
|
tooltip.classList.add(this.options.className);
|
|
100
109
|
}
|
package/js/tooltip.ts
CHANGED
|
@@ -7,6 +7,9 @@ interface TooltipOptions {
|
|
|
7
7
|
offset?: number;
|
|
8
8
|
delay?: number;
|
|
9
9
|
className?: string;
|
|
10
|
+
/** Set to true when content is trusted HTML (e.g. from data-tooltip-id).
|
|
11
|
+
* Defaults to false — content is treated as plain text and escaped. */
|
|
12
|
+
isHtml?: boolean;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
class Tooltip {
|
|
@@ -27,7 +30,8 @@ class Tooltip {
|
|
|
27
30
|
position: options.position ?? 'auto',
|
|
28
31
|
offset: options.offset ?? 8,
|
|
29
32
|
delay: options.delay ?? 0,
|
|
30
|
-
className: options.className ?? ''
|
|
33
|
+
className: options.className ?? '',
|
|
34
|
+
isHtml: options.isHtml ?? false,
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
this.attachEvents();
|
|
@@ -41,7 +45,7 @@ class Tooltip {
|
|
|
41
45
|
const className = trigger.getAttribute('data-tooltip-class') ?? '';
|
|
42
46
|
|
|
43
47
|
if (content) {
|
|
44
|
-
new Tooltip(trigger, content, { position, className });
|
|
48
|
+
new Tooltip(trigger, content, { position, className, isHtml: false });
|
|
45
49
|
}
|
|
46
50
|
});
|
|
47
51
|
|
|
@@ -56,7 +60,7 @@ class Tooltip {
|
|
|
56
60
|
const contentElement = document.getElementById(contentId);
|
|
57
61
|
if (contentElement) {
|
|
58
62
|
const content = contentElement.innerHTML;
|
|
59
|
-
new Tooltip(trigger, content, { position, className });
|
|
63
|
+
new Tooltip(trigger, content, { position, className, isHtml: true });
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
});
|
|
@@ -113,7 +117,15 @@ class Tooltip {
|
|
|
113
117
|
tooltip.className = 'tooltip';
|
|
114
118
|
tooltip.id = `tooltip-${++Tooltip.idCounter}`;
|
|
115
119
|
tooltip.setAttribute('role', 'tooltip');
|
|
116
|
-
|
|
120
|
+
|
|
121
|
+
const tooltipContent = document.createElement('div');
|
|
122
|
+
tooltipContent.className = 'tooltip-content';
|
|
123
|
+
if (this.options.isHtml) {
|
|
124
|
+
tooltipContent.innerHTML = this.content;
|
|
125
|
+
} else {
|
|
126
|
+
tooltipContent.textContent = this.content;
|
|
127
|
+
}
|
|
128
|
+
tooltip.appendChild(tooltipContent);
|
|
117
129
|
|
|
118
130
|
if (this.options.className) {
|
|
119
131
|
tooltip.classList.add(this.options.className);
|
package/js/tree.js
CHANGED
package/js/tree.ts
CHANGED
package/js/utils.js
CHANGED
|
@@ -66,4 +66,32 @@ const utils = {
|
|
|
66
66
|
return element.offsetParent === null;
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Escape a plain-text string so it is safe to inject into innerHTML.
|
|
71
|
+
* Use this whenever inserting user-controlled strings into HTML templates.
|
|
72
|
+
*/
|
|
73
|
+
function escapeHtml(text) {
|
|
74
|
+
const div = document.createElement('div');
|
|
75
|
+
div.textContent = text;
|
|
76
|
+
return div.innerHTML;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Sanitize an HTML string by removing dangerous elements and attributes
|
|
80
|
+
* (script, iframe, on* handlers, javascript: hrefs) while preserving safe markup.
|
|
81
|
+
* Use this when a component intentionally accepts rich HTML from callers.
|
|
82
|
+
*/
|
|
83
|
+
function sanitizeHtml(html) {
|
|
84
|
+
const parser = new DOMParser();
|
|
85
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
86
|
+
doc.querySelectorAll('script, style, iframe, object, embed').forEach(el => el.remove());
|
|
87
|
+
doc.querySelectorAll('*').forEach(el => {
|
|
88
|
+
for (const attr of Array.from(el.attributes)) {
|
|
89
|
+
if (attr.name.startsWith('on') ||
|
|
90
|
+
attr.value.trim().toLowerCase().startsWith('javascript:')) {
|
|
91
|
+
el.removeAttribute(attr.name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return doc.body.innerHTML;
|
|
96
|
+
}
|
|
97
|
+
export { utils, escapeHtml, sanitizeHtml };
|
package/js/utils.ts
CHANGED
|
@@ -81,4 +81,39 @@ const utils: Utils = {
|
|
|
81
81
|
}
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Escape a plain-text string so it is safe to inject into innerHTML.
|
|
86
|
+
* Use this whenever inserting user-controlled strings into HTML templates.
|
|
87
|
+
*/
|
|
88
|
+
function escapeHtml(text: string): string {
|
|
89
|
+
const div = document.createElement('div');
|
|
90
|
+
div.textContent = text;
|
|
91
|
+
return div.innerHTML;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Sanitize an HTML string by removing dangerous elements and attributes
|
|
96
|
+
* (script, iframe, on* handlers, javascript: hrefs) while preserving safe markup.
|
|
97
|
+
* Use this when a component intentionally accepts rich HTML from callers.
|
|
98
|
+
*/
|
|
99
|
+
function sanitizeHtml(html: string): string {
|
|
100
|
+
const parser = new DOMParser();
|
|
101
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
102
|
+
|
|
103
|
+
doc.querySelectorAll('script, style, iframe, object, embed').forEach(el => el.remove());
|
|
104
|
+
|
|
105
|
+
doc.querySelectorAll('*').forEach(el => {
|
|
106
|
+
for (const attr of Array.from(el.attributes)) {
|
|
107
|
+
if (
|
|
108
|
+
attr.name.startsWith('on') ||
|
|
109
|
+
attr.value.trim().toLowerCase().startsWith('javascript:')
|
|
110
|
+
) {
|
|
111
|
+
el.removeAttribute(attr.name);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return doc.body.innerHTML;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { utils, escapeHtml, sanitizeHtml };
|
package/js/virtual-dropdown.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escapeHtml } from './utils.js';
|
|
1
2
|
class VirtualDropdown {
|
|
2
3
|
constructor(config) {
|
|
3
4
|
const containerElement = typeof config.container === 'string'
|
|
@@ -30,7 +31,7 @@ class VirtualDropdown {
|
|
|
30
31
|
renderBase() {
|
|
31
32
|
this.container.innerHTML = `
|
|
32
33
|
<div class="dropdown-trigger" tabindex="0" role="button" aria-haspopup="listbox" aria-expanded="false">
|
|
33
|
-
<span class="trigger-text">${
|
|
34
|
+
<span class="trigger-text">${escapeHtml(this.placeholder)}</span>
|
|
34
35
|
<span class="trigger-arrow" aria-hidden="true">▼</span>
|
|
35
36
|
</div>
|
|
36
37
|
<div class="dropdown-menu" role="listbox">
|
|
@@ -146,14 +147,14 @@ class VirtualDropdown {
|
|
|
146
147
|
const isSelected = this.selectedValues.has(opt.value);
|
|
147
148
|
return `
|
|
148
149
|
<div class="dropdown-item ${isSelected ? 'selected' : ''}"
|
|
149
|
-
data-value="${
|
|
150
|
+
data-value="${escapeHtml(String(opt.value))}"
|
|
150
151
|
data-idx="${realIdx}"
|
|
151
152
|
role="option"
|
|
152
153
|
aria-selected="${isSelected}"
|
|
153
154
|
tabindex="0"
|
|
154
155
|
style="height: ${this.itemHeight}px; line-height: ${this.itemHeight}px;">
|
|
155
156
|
${this.multiSelect ? `<input type="checkbox" ${isSelected ? 'checked' : ''} tabindex="-1" aria-hidden="true">` : ''}
|
|
156
|
-
<span class="item-label">${
|
|
157
|
+
<span class="item-label">${escapeHtml(opt.label)}</span>
|
|
157
158
|
</div>
|
|
158
159
|
`;
|
|
159
160
|
})
|
|
@@ -221,11 +222,6 @@ class VirtualDropdown {
|
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
|
-
escapeHtml(text) {
|
|
225
|
-
const div = document.createElement('div');
|
|
226
|
-
div.textContent = text;
|
|
227
|
-
return div.innerHTML;
|
|
228
|
-
}
|
|
229
225
|
getValue() {
|
|
230
226
|
return Array.from(this.selectedValues);
|
|
231
227
|
}
|
package/js/virtual-dropdown.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { escapeHtml } from './utils.js';
|
|
2
|
+
|
|
1
3
|
interface DropdownOption {
|
|
2
4
|
label: string;
|
|
3
5
|
value: string | number;
|
|
@@ -77,7 +79,7 @@ class VirtualDropdown {
|
|
|
77
79
|
private renderBase(): void {
|
|
78
80
|
this.container.innerHTML = `
|
|
79
81
|
<div class="dropdown-trigger" tabindex="0" role="button" aria-haspopup="listbox" aria-expanded="false">
|
|
80
|
-
<span class="trigger-text">${
|
|
82
|
+
<span class="trigger-text">${escapeHtml(this.placeholder)}</span>
|
|
81
83
|
<span class="trigger-arrow" aria-hidden="true">▼</span>
|
|
82
84
|
</div>
|
|
83
85
|
<div class="dropdown-menu" role="listbox">
|
|
@@ -215,14 +217,14 @@ class VirtualDropdown {
|
|
|
215
217
|
const isSelected = this.selectedValues.has(opt.value);
|
|
216
218
|
return `
|
|
217
219
|
<div class="dropdown-item ${isSelected ? 'selected' : ''}"
|
|
218
|
-
data-value="${
|
|
220
|
+
data-value="${escapeHtml(String(opt.value))}"
|
|
219
221
|
data-idx="${realIdx}"
|
|
220
222
|
role="option"
|
|
221
223
|
aria-selected="${isSelected}"
|
|
222
224
|
tabindex="0"
|
|
223
225
|
style="height: ${this.itemHeight}px; line-height: ${this.itemHeight}px;">
|
|
224
226
|
${this.multiSelect ? `<input type="checkbox" ${isSelected ? 'checked' : ''} tabindex="-1" aria-hidden="true">` : ''}
|
|
225
|
-
<span class="item-label">${
|
|
227
|
+
<span class="item-label">${escapeHtml(opt.label)}</span>
|
|
226
228
|
</div>
|
|
227
229
|
`;
|
|
228
230
|
})
|
|
@@ -299,12 +301,6 @@ class VirtualDropdown {
|
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
303
|
|
|
302
|
-
private escapeHtml(text: string): string {
|
|
303
|
-
const div = document.createElement('div');
|
|
304
|
-
div.textContent = text;
|
|
305
|
-
return div.innerHTML;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
304
|
public getValue(): Array<string | number> {
|
|
309
305
|
return Array.from(this.selectedValues);
|
|
310
306
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dodlhuat/basix",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./css/*": "./css/*",
|