selective-ui 1.4.0 → 1.4.2
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/selective-ui.css +2 -2
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +407 -573
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +2 -2
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.css +1 -1
- package/dist/selective-ui.min.css.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +409 -575
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +12 -12
- package/src/css/views/option-view.css +2 -2
- package/src/ts/adapter/mixed-adapter.ts +149 -71
- package/src/ts/components/accessorybox.ts +14 -11
- package/src/ts/components/directive.ts +1 -1
- package/src/ts/components/option-handle.ts +12 -9
- package/src/ts/components/placeholder.ts +5 -5
- package/src/ts/components/popup/empty-state.ts +5 -5
- package/src/ts/components/popup/loading-state.ts +5 -5
- package/src/ts/components/popup/popup.ts +138 -76
- package/src/ts/components/searchbox.ts +17 -13
- package/src/ts/components/selectbox.ts +260 -84
- package/src/ts/core/base/adapter.ts +61 -14
- package/src/ts/core/base/fenwick.ts +3 -2
- package/src/ts/core/base/lifecycle.ts +14 -4
- package/src/ts/core/base/model.ts +17 -15
- package/src/ts/core/base/recyclerview.ts +7 -5
- package/src/ts/core/base/view.ts +10 -5
- package/src/ts/core/base/virtual-recyclerview.ts +178 -45
- package/src/ts/core/model-manager.ts +48 -21
- package/src/ts/core/search-controller.ts +174 -56
- package/src/ts/global.ts +5 -8
- package/src/ts/index.ts +2 -2
- package/src/ts/models/group-model.ts +33 -8
- package/src/ts/models/option-model.ts +88 -20
- package/src/ts/services/dataset-observer.ts +6 -3
- package/src/ts/services/ea-observer.ts +1 -1
- package/src/ts/services/effector.ts +22 -12
- package/src/ts/services/refresher.ts +14 -4
- package/src/ts/services/resize-observer.ts +24 -11
- package/src/ts/services/select-observer.ts +2 -2
- package/src/ts/types/components/popup.type.ts +18 -1
- package/src/ts/types/components/searchbox.type.ts +43 -30
- package/src/ts/types/components/state.box.type.ts +1 -1
- package/src/ts/types/core/base/adapter.type.ts +13 -5
- package/src/ts/types/core/base/lifecycle.type.ts +1 -2
- package/src/ts/types/core/base/model.type.ts +3 -3
- package/src/ts/types/core/base/recyclerview.type.ts +7 -5
- package/src/ts/types/core/base/view.type.ts +6 -6
- package/src/ts/types/core/base/virtual-recyclerview.type.ts +45 -46
- package/src/ts/types/core/search-controller.type.ts +18 -2
- package/src/ts/types/css.d.ts +1 -0
- package/src/ts/types/plugins/plugin.type.ts +2 -2
- package/src/ts/types/services/effector.type.ts +25 -25
- package/src/ts/types/services/resize-observer.type.ts +23 -12
- package/src/ts/types/utils/callback-scheduler.type.ts +2 -2
- package/src/ts/types/utils/ievents.type.ts +1 -1
- package/src/ts/types/utils/istorage.type.ts +62 -60
- package/src/ts/types/utils/libs.type.ts +19 -17
- package/src/ts/types/utils/selective.type.ts +6 -3
- package/src/ts/types/views/view.group.type.ts +9 -5
- package/src/ts/types/views/view.option.type.ts +39 -17
- package/src/ts/utils/callback-scheduler.ts +12 -7
- package/src/ts/utils/ievents.ts +12 -5
- package/src/ts/utils/istorage.ts +5 -3
- package/src/ts/utils/libs.ts +122 -43
- package/src/ts/utils/selective.ts +15 -8
- package/src/ts/views/group-view.ts +11 -9
- package/src/ts/views/option-view.ts +37 -18
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
* Includes attributes, styles, events, and accessibility properties.
|
|
4
4
|
*/
|
|
5
5
|
export type NodeSpec = {
|
|
6
|
-
node: string;
|
|
7
|
-
classList?: string | string[];
|
|
8
|
-
style?: Partial<CSSStyleDeclaration>;
|
|
9
|
-
dataset?: Record<string, string>;
|
|
10
|
-
role?: string;
|
|
11
|
-
ariaLive?: string;
|
|
12
|
-
ariaLabelledby?: string;
|
|
13
|
-
ariaControls?: string;
|
|
14
|
-
ariaHaspopup?: string;
|
|
15
|
-
ariaMultiselectable?: string;
|
|
16
|
-
ariaAutocomplete?: string;
|
|
17
|
-
event?: Record<string, EventListener>;
|
|
18
|
-
[key: string]: unknown;
|
|
6
|
+
node: string; // Tag name of the node (e.g., "div", "span")
|
|
7
|
+
classList?: string | string[]; // CSS classes to apply
|
|
8
|
+
style?: Partial<CSSStyleDeclaration>; // Inline styles for the node
|
|
9
|
+
dataset?: Record<string, string>; // Data attributes (e.g., data-* values)
|
|
10
|
+
role?: string; // ARIA role for accessibility
|
|
11
|
+
ariaLive?: string; // ARIA live region setting
|
|
12
|
+
ariaLabelledby?: string; // ARIA labelledby reference
|
|
13
|
+
ariaControls?: string; // ARIA controls reference
|
|
14
|
+
ariaHaspopup?: string; // ARIA haspopup attribute
|
|
15
|
+
ariaMultiselectable?: string; // ARIA multiselectable attribute
|
|
16
|
+
ariaAutocomplete?: string; // ARIA autocomplete attribute
|
|
17
|
+
event?: Record<string, EventListener>; // Event listeners mapped by event name
|
|
18
|
+
[key: string]: unknown; // Allow additional custom properties
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -24,7 +24,9 @@ export type NodeSpec = {
|
|
|
24
24
|
*
|
|
25
25
|
* @template TTags - A map of tag names to their corresponding HTMLElement instances.
|
|
26
26
|
*/
|
|
27
|
-
export type MountViewResult<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
export type MountViewResult<
|
|
28
|
+
TTags extends Record<string, HTMLElement> = Record<string, HTMLElement>,
|
|
29
|
+
> = {
|
|
30
|
+
view?: HTMLElement; // Root element of the mounted view
|
|
31
|
+
tags: TTags & { id: string }; // Tag map with an additional unique ID
|
|
32
|
+
};
|
|
@@ -7,9 +7,12 @@ import type { SelectivePlugin } from "../plugins/plugin.type";
|
|
|
7
7
|
* Extends DefaultConfig with additional internal identifiers.
|
|
8
8
|
*/
|
|
9
9
|
export type SelectiveOptions = DefaultConfig & {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/** Unique Selective Element ID */
|
|
11
|
+
SEID?: string;
|
|
12
|
+
/** ID for the list container */
|
|
13
|
+
SEID_LIST?: string;
|
|
14
|
+
/** ID for the holder element */
|
|
15
|
+
SEID_HOLDER?: string;
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -5,9 +5,12 @@ import { MountViewResult } from "../utils/libs.type";
|
|
|
5
5
|
* These tags correspond to key sections of the group UI.
|
|
6
6
|
*/
|
|
7
7
|
export type GroupViewTags = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/** Root container for the group view */
|
|
9
|
+
GroupView: HTMLDivElement;
|
|
10
|
+
/** Header section displaying the group title */
|
|
11
|
+
GroupHeader: HTMLDivElement;
|
|
12
|
+
/** Container for the group's items */
|
|
13
|
+
GroupItems: HTMLDivElement;
|
|
11
14
|
};
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -15,5 +18,6 @@ export type GroupViewTags = {
|
|
|
15
18
|
* Extends MountViewResult with a guaranteed root element (`view`).
|
|
16
19
|
*/
|
|
17
20
|
export type GroupViewResult = MountViewResult<GroupViewTags> & {
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
/** The root element of the mounted group view */
|
|
22
|
+
view: Element;
|
|
23
|
+
};
|
|
@@ -5,11 +5,16 @@ import { MountViewResult } from "../utils/libs.type";
|
|
|
5
5
|
* These tags correspond to key elements of the option UI.
|
|
6
6
|
*/
|
|
7
7
|
export type OptionViewTags = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/** Root container for the option view */
|
|
9
|
+
OptionView: HTMLDivElement;
|
|
10
|
+
/** Input element (checkbox or radio) */
|
|
11
|
+
OptionInput: HTMLInputElement;
|
|
12
|
+
/** Label element for the option */
|
|
13
|
+
OptionLabel: HTMLLabelElement;
|
|
14
|
+
/** Container for label text content */
|
|
15
|
+
LabelContent: HTMLDivElement;
|
|
16
|
+
/** Image element for options with images */
|
|
17
|
+
OptionImage: HTMLImageElement;
|
|
13
18
|
};
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -17,7 +22,8 @@ export type OptionViewTags = {
|
|
|
17
22
|
* Extends MountViewResult with a guaranteed root element (`view`).
|
|
18
23
|
*/
|
|
19
24
|
export type OptionViewResult = MountViewResult<OptionViewTags> & {
|
|
20
|
-
|
|
25
|
+
/** The root element of the mounted option view */
|
|
26
|
+
view: Element;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
/**
|
|
@@ -39,20 +45,36 @@ export type LabelHalign = "left" | "center" | "right";
|
|
|
39
45
|
* Configuration options for rendering an option.
|
|
40
46
|
*/
|
|
41
47
|
export type OptionConfig = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
/** Indicates if multiple selection is allowed */
|
|
49
|
+
isMultiple: boolean;
|
|
50
|
+
/** Indicates if the option includes an image */
|
|
51
|
+
hasImage: boolean;
|
|
52
|
+
/** Position of the image relative to the label */
|
|
53
|
+
imagePosition: ImagePosition;
|
|
54
|
+
/** Width of the image */
|
|
55
|
+
imageWidth: string;
|
|
56
|
+
/** Height of the image */
|
|
57
|
+
imageHeight: string;
|
|
58
|
+
/** Border radius for the image */
|
|
59
|
+
imageBorderRadius: string;
|
|
60
|
+
/** Vertical alignment of the label */
|
|
61
|
+
labelValign: LabelValign;
|
|
62
|
+
/** Horizontal alignment of the label */
|
|
63
|
+
labelHalign: LabelHalign;
|
|
50
64
|
};
|
|
51
65
|
|
|
52
66
|
/**
|
|
53
67
|
* Partial configuration patch for updating specific option properties.
|
|
54
68
|
* Includes only image and label alignment-related properties.
|
|
55
69
|
*/
|
|
56
|
-
export type OptionConfigPatch = Partial<
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
export type OptionConfigPatch = Partial<
|
|
71
|
+
Pick<
|
|
72
|
+
OptionConfig,
|
|
73
|
+
| "imageWidth"
|
|
74
|
+
| "imageHeight"
|
|
75
|
+
| "imageBorderRadius"
|
|
76
|
+
| "imagePosition"
|
|
77
|
+
| "labelValign"
|
|
78
|
+
| "labelHalign"
|
|
79
|
+
>
|
|
80
|
+
>;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
StoredEntry,
|
|
3
|
+
TimerKey,
|
|
4
|
+
TimerOptions,
|
|
5
|
+
} from "../types/utils/callback-scheduler.type";
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* CallbackScheduler
|
|
@@ -82,14 +86,14 @@ export class CallbackScheduler {
|
|
|
82
86
|
*
|
|
83
87
|
* @public
|
|
84
88
|
* @param {TimerKey} key - Group identifier for callbacks.
|
|
85
|
-
* @param {(payload
|
|
89
|
+
* @param {(payload?: any[]) => void} callback - Function to execute after debounce timeout.
|
|
86
90
|
* @param {TimerOptions} [options={}] - Scheduling options (`debounce`, `once`).
|
|
87
91
|
* @returns {void}
|
|
88
92
|
*/
|
|
89
93
|
public on(
|
|
90
94
|
key: TimerKey,
|
|
91
|
-
callback: (payload
|
|
92
|
-
options: TimerOptions = {}
|
|
95
|
+
callback: (payload?: any[]) => void,
|
|
96
|
+
options: TimerOptions = {},
|
|
93
97
|
): void {
|
|
94
98
|
const timeout = options.debounce ?? 50;
|
|
95
99
|
const once = options.once ?? false;
|
|
@@ -176,13 +180,14 @@ export class CallbackScheduler {
|
|
|
176
180
|
const timer = setTimeout(async () => {
|
|
177
181
|
try {
|
|
178
182
|
const resp = entry.callback(
|
|
179
|
-
params.length > 0 ? params : null
|
|
183
|
+
params.length > 0 ? params : null,
|
|
180
184
|
) as any;
|
|
181
185
|
|
|
182
186
|
if (resp instanceof Promise) {
|
|
183
187
|
await resp;
|
|
184
188
|
}
|
|
185
|
-
} catch {
|
|
189
|
+
} catch {
|
|
190
|
+
} finally {
|
|
186
191
|
if (entry.once) {
|
|
187
192
|
executes[i] = undefined;
|
|
188
193
|
|
|
@@ -230,4 +235,4 @@ export class CallbackScheduler {
|
|
|
230
235
|
this.off(k);
|
|
231
236
|
}
|
|
232
237
|
}
|
|
233
|
-
}
|
|
238
|
+
}
|
package/src/ts/utils/ievents.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
IEventCallback,
|
|
3
|
+
IEventHandler,
|
|
4
|
+
IEventToken,
|
|
5
|
+
} from "../types/utils/ievents.type";
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* iEvents
|
|
@@ -58,7 +62,10 @@ export class iEvents {
|
|
|
58
62
|
* - `token`: immutable view of the dispatch state.
|
|
59
63
|
* - `callback`: controller passed into handlers to modify dispatch flow.
|
|
60
64
|
*/
|
|
61
|
-
public static buildEventToken(): {
|
|
65
|
+
public static buildEventToken(): {
|
|
66
|
+
token: IEventToken;
|
|
67
|
+
callback: IEventCallback;
|
|
68
|
+
} {
|
|
62
69
|
const privToken = { isContinue: true, isCancel: false };
|
|
63
70
|
|
|
64
71
|
const token: IEventToken = {
|
|
@@ -104,7 +111,7 @@ export class iEvents {
|
|
|
104
111
|
* @returns The {@link IEventToken} describing the final dispatch state.
|
|
105
112
|
*/
|
|
106
113
|
public static callEvent<TParams extends unknown[]>(
|
|
107
|
-
params
|
|
114
|
+
params?: TParams,
|
|
108
115
|
...handles: Array<IEventHandler<TParams> | unknown>
|
|
109
116
|
): IEventToken {
|
|
110
117
|
const { token, callback } = this.buildEventToken();
|
|
@@ -139,7 +146,7 @@ export class iEvents {
|
|
|
139
146
|
public static trigger(
|
|
140
147
|
element: HTMLElement | Window | Document,
|
|
141
148
|
eventType: string,
|
|
142
|
-
opts: EventInit = { bubbles: true, cancelable: true }
|
|
149
|
+
opts: EventInit = { bubbles: true, cancelable: true },
|
|
143
150
|
): Event {
|
|
144
151
|
const evt = new Event(eventType, opts);
|
|
145
152
|
element.dispatchEvent(evt);
|
|
@@ -167,4 +174,4 @@ export class iEvents {
|
|
|
167
174
|
(fn as (...args: TParams) => unknown)(...params);
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
|
-
}
|
|
177
|
+
}
|
package/src/ts/utils/istorage.ts
CHANGED
|
@@ -61,11 +61,13 @@ export class iStorage {
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
/** Bound instance map (keyed by select element). */
|
|
64
|
-
public bindedMap: Map<HTMLSelectElement|HTMLElement, BinderMap> =
|
|
64
|
+
public bindedMap: Map<HTMLSelectElement | HTMLElement, BinderMap> =
|
|
65
|
+
new Map();
|
|
65
66
|
|
|
66
67
|
/** Unbind cache map (keyed by select element). */
|
|
67
|
-
public unbindedMap: Map<HTMLSelectElement|HTMLElement, BinderMap> =
|
|
68
|
+
public unbindedMap: Map<HTMLSelectElement | HTMLElement, BinderMap> =
|
|
69
|
+
new Map();
|
|
68
70
|
|
|
69
71
|
/** List of bound selectors/commands. */
|
|
70
72
|
public bindedCommand: string[] = [];
|
|
71
|
-
}
|
|
73
|
+
}
|
package/src/ts/utils/libs.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { SelectiveOptions } from "../types/utils/selective.type";
|
|
|
8
8
|
* @class
|
|
9
9
|
*/
|
|
10
10
|
export class Libs {
|
|
11
|
-
private static _iStorage
|
|
11
|
+
private static _iStorage?: iStorage;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Retrieves the shared iStorage instance (lazy-initialized singleton).
|
|
@@ -52,10 +52,13 @@ export class Libs {
|
|
|
52
52
|
*/
|
|
53
53
|
public static randomString(length: number = 6): string {
|
|
54
54
|
let result = "";
|
|
55
|
-
const characters =
|
|
55
|
+
const characters =
|
|
56
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
56
57
|
const charactersLength = characters.length;
|
|
57
58
|
for (let i = 0; i < length; i++) {
|
|
58
|
-
result += characters.charAt(
|
|
59
|
+
result += characters.charAt(
|
|
60
|
+
Math.floor(Math.random() * charactersLength),
|
|
61
|
+
);
|
|
59
62
|
}
|
|
60
63
|
return result;
|
|
61
64
|
}
|
|
@@ -75,12 +78,13 @@ export class Libs {
|
|
|
75
78
|
| HTMLElement
|
|
76
79
|
| ArrayLike<HTMLElement>
|
|
77
80
|
| null
|
|
78
|
-
| undefined
|
|
81
|
+
| undefined,
|
|
79
82
|
): T {
|
|
80
83
|
if (!queryCommon) return [] as T;
|
|
81
84
|
|
|
82
85
|
if (typeof queryCommon === "string") {
|
|
83
|
-
const nodeList =
|
|
86
|
+
const nodeList =
|
|
87
|
+
document.querySelectorAll<HTMLElement>(queryCommon);
|
|
84
88
|
return Array.from(nodeList) as T;
|
|
85
89
|
}
|
|
86
90
|
|
|
@@ -102,9 +106,15 @@ export class Libs {
|
|
|
102
106
|
* @param {NodeSpec} data - Specification describing the element to create.
|
|
103
107
|
* @returns {HTMLElement} - The created element.
|
|
104
108
|
*/
|
|
105
|
-
public static nodeCreator<T extends HTMLElement>(
|
|
109
|
+
public static nodeCreator<T extends HTMLElement>(
|
|
110
|
+
data: Partial<NodeSpec> = {},
|
|
111
|
+
): T {
|
|
106
112
|
const nodeName = (data.node ?? "div") as string;
|
|
107
|
-
return this.nodeCloner<T>(
|
|
113
|
+
return this.nodeCloner<T>(
|
|
114
|
+
document.createElement(nodeName),
|
|
115
|
+
data as NodeSpec,
|
|
116
|
+
true,
|
|
117
|
+
);
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
/**
|
|
@@ -116,10 +126,16 @@ export class Libs {
|
|
|
116
126
|
* @param {boolean} systemNodeCreate - If true, do not clone; use original node.
|
|
117
127
|
* @returns {HTMLElement} - The processed element.
|
|
118
128
|
*/
|
|
119
|
-
public static nodeCloner<T extends HTMLElement>(
|
|
129
|
+
public static nodeCloner<T extends HTMLElement>(
|
|
130
|
+
node: HTMLElement = document.documentElement,
|
|
131
|
+
_nodeOption?: NodeSpec,
|
|
132
|
+
systemNodeCreate = false,
|
|
133
|
+
): T {
|
|
120
134
|
const nodeOption: Record<string, unknown> = { ...(_nodeOption ?? {}) };
|
|
121
135
|
|
|
122
|
-
const element_creation: T = systemNodeCreate
|
|
136
|
+
const element_creation: T = systemNodeCreate
|
|
137
|
+
? (node as T)
|
|
138
|
+
: (node.cloneNode(true) as T);
|
|
123
139
|
|
|
124
140
|
const classList = nodeOption.classList;
|
|
125
141
|
if (typeof classList === "string") {
|
|
@@ -142,32 +158,52 @@ export class Libs {
|
|
|
142
158
|
delete nodeOption.role;
|
|
143
159
|
}
|
|
144
160
|
if (nodeOption.ariaLive) {
|
|
145
|
-
element_creation.setAttribute(
|
|
161
|
+
element_creation.setAttribute(
|
|
162
|
+
"aria-live",
|
|
163
|
+
String(nodeOption.ariaLive),
|
|
164
|
+
);
|
|
146
165
|
delete nodeOption.ariaLive;
|
|
147
166
|
}
|
|
148
167
|
if (nodeOption.ariaLabelledby) {
|
|
149
|
-
element_creation.setAttribute(
|
|
168
|
+
element_creation.setAttribute(
|
|
169
|
+
"aria-labelledby",
|
|
170
|
+
String(nodeOption.ariaLabelledby),
|
|
171
|
+
);
|
|
150
172
|
delete nodeOption.ariaLabelledby;
|
|
151
173
|
}
|
|
152
174
|
if (nodeOption.ariaControls) {
|
|
153
|
-
element_creation.setAttribute(
|
|
175
|
+
element_creation.setAttribute(
|
|
176
|
+
"aria-controls",
|
|
177
|
+
String(nodeOption.ariaControls),
|
|
178
|
+
);
|
|
154
179
|
delete nodeOption.ariaControls;
|
|
155
180
|
}
|
|
156
181
|
if (nodeOption.ariaHaspopup) {
|
|
157
|
-
element_creation.setAttribute(
|
|
182
|
+
element_creation.setAttribute(
|
|
183
|
+
"aria-haspopup",
|
|
184
|
+
String(nodeOption.ariaHaspopup),
|
|
185
|
+
);
|
|
158
186
|
delete nodeOption.ariaHaspopup;
|
|
159
187
|
}
|
|
160
188
|
if (nodeOption.ariaMultiselectable) {
|
|
161
|
-
element_creation.setAttribute(
|
|
189
|
+
element_creation.setAttribute(
|
|
190
|
+
"aria-multiselectable",
|
|
191
|
+
String(nodeOption.ariaMultiselectable),
|
|
192
|
+
);
|
|
162
193
|
delete nodeOption.ariaMultiselectable;
|
|
163
194
|
}
|
|
164
195
|
if (nodeOption.ariaAutocomplete) {
|
|
165
|
-
element_creation.setAttribute(
|
|
196
|
+
element_creation.setAttribute(
|
|
197
|
+
"aria-autocomplete",
|
|
198
|
+
String(nodeOption.ariaAutocomplete),
|
|
199
|
+
);
|
|
166
200
|
delete nodeOption.ariaAutocomplete;
|
|
167
201
|
}
|
|
168
202
|
|
|
169
203
|
if (nodeOption.event && typeof nodeOption.event === "object") {
|
|
170
|
-
Object.entries(
|
|
204
|
+
Object.entries(
|
|
205
|
+
nodeOption.event as Record<string, EventListener>,
|
|
206
|
+
).forEach(([key, value]) => {
|
|
171
207
|
element_creation.addEventListener(key, value);
|
|
172
208
|
});
|
|
173
209
|
delete nodeOption.event;
|
|
@@ -198,21 +234,29 @@ export class Libs {
|
|
|
198
234
|
*/
|
|
199
235
|
public static mountNode<TTags extends Record<string, any>>(
|
|
200
236
|
rawObj: Record<string, any>,
|
|
201
|
-
parentE
|
|
237
|
+
parentE?: HTMLElement,
|
|
202
238
|
isPrepend = false,
|
|
203
239
|
isRecusive = false,
|
|
204
|
-
recursiveTemp: any = {}
|
|
240
|
+
recursiveTemp: any = {},
|
|
205
241
|
): TTags {
|
|
206
242
|
let view: HTMLElement | null = null;
|
|
207
243
|
|
|
208
244
|
for (const key in rawObj) {
|
|
209
245
|
const singleObj = rawObj[key];
|
|
210
|
-
const tag: HTMLElement =
|
|
211
|
-
|
|
246
|
+
const tag: HTMLElement = singleObj?.tag?.tagName
|
|
247
|
+
? (singleObj.tag as HTMLElement)
|
|
248
|
+
: (this.nodeCreator(singleObj.tag) as HTMLElement);
|
|
212
249
|
|
|
213
250
|
recursiveTemp[key] = tag;
|
|
214
251
|
|
|
215
|
-
if (singleObj?.child)
|
|
252
|
+
if (singleObj?.child)
|
|
253
|
+
this.mountNode<TTags>(
|
|
254
|
+
singleObj.child,
|
|
255
|
+
tag,
|
|
256
|
+
false,
|
|
257
|
+
false,
|
|
258
|
+
recursiveTemp,
|
|
259
|
+
);
|
|
216
260
|
|
|
217
261
|
if (parentE) {
|
|
218
262
|
if (isPrepend) parentE.prepend(tag);
|
|
@@ -240,7 +284,10 @@ export class Libs {
|
|
|
240
284
|
* @param {SelectiveOptions} options - Default configuration to be merged.
|
|
241
285
|
* @returns {SelectiveOptions} - Final configuration after element overrides.
|
|
242
286
|
*/
|
|
243
|
-
public static buildConfig(
|
|
287
|
+
public static buildConfig(
|
|
288
|
+
element: HTMLElement,
|
|
289
|
+
options: SelectiveOptions,
|
|
290
|
+
): SelectiveOptions {
|
|
244
291
|
const myOptions = this.jsCopyObject<SelectiveOptions>(options);
|
|
245
292
|
|
|
246
293
|
for (const optionKey in myOptions) {
|
|
@@ -248,13 +295,14 @@ export class Libs {
|
|
|
248
295
|
if (propValue) {
|
|
249
296
|
if (typeof myOptions[optionKey] === "boolean") {
|
|
250
297
|
myOptions[optionKey] = this.string2Boolean(propValue);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
298
|
+
} else {
|
|
253
299
|
myOptions[optionKey] = propValue;
|
|
254
300
|
}
|
|
255
301
|
} else if (typeof element?.dataset?.[optionKey] !== "undefined") {
|
|
256
302
|
if (typeof myOptions[optionKey] === "boolean") {
|
|
257
|
-
myOptions[optionKey] = this.string2Boolean(
|
|
303
|
+
myOptions[optionKey] = this.string2Boolean(
|
|
304
|
+
element.dataset[optionKey],
|
|
305
|
+
);
|
|
258
306
|
} else {
|
|
259
307
|
myOptions[optionKey] = element.dataset[optionKey];
|
|
260
308
|
}
|
|
@@ -271,7 +319,9 @@ export class Libs {
|
|
|
271
319
|
* @param {...object} params - Config objects in priority order (leftmost is base).
|
|
272
320
|
* @returns {object} - Merged configuration object.
|
|
273
321
|
*/
|
|
274
|
-
public static mergeConfig<T extends Record<string, any>>(
|
|
322
|
+
public static mergeConfig<T extends Record<string, any>>(
|
|
323
|
+
...params: T[]
|
|
324
|
+
): T {
|
|
275
325
|
if (params.length === 0) return {} as T;
|
|
276
326
|
if (params.length === 1) return this.jsCopyObject(params[0]);
|
|
277
327
|
|
|
@@ -338,7 +388,9 @@ export class Libs {
|
|
|
338
388
|
* @param {HTMLElement} item - HTMLElement key whose binder map is requested.
|
|
339
389
|
* @returns {BinderMap | any} - The stored binder map value or undefined if absent.
|
|
340
390
|
*/
|
|
341
|
-
public static getBinderMap<T extends BinderMap | any>(
|
|
391
|
+
public static getBinderMap<T extends BinderMap | any>(
|
|
392
|
+
item: HTMLElement,
|
|
393
|
+
): T {
|
|
342
394
|
return this.iStorage.bindedMap.get(item) as T;
|
|
343
395
|
}
|
|
344
396
|
|
|
@@ -425,26 +477,34 @@ export class Libs {
|
|
|
425
477
|
.replace(/<iframe\b[^>]*>[\s\S]*?<\/iframe>/gi, "")
|
|
426
478
|
.replace(/<(object|embed|link)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
|
|
427
479
|
s = s.replace(/\son[a-z]+\s*=\s*(['"]).*?\1/gi, "");
|
|
428
|
-
s = s.replace(
|
|
480
|
+
s = s.replace(
|
|
481
|
+
/\s([a-z-:]+)\s*=\s*(['"])\s*javascript:[^'"]*\2/gi,
|
|
482
|
+
"",
|
|
483
|
+
);
|
|
429
484
|
return s;
|
|
430
485
|
}
|
|
431
486
|
|
|
432
487
|
const tmp = doc.createElement("div");
|
|
433
488
|
tmp.innerHTML = s;
|
|
434
489
|
|
|
435
|
-
tmp.querySelectorAll(
|
|
490
|
+
tmp.querySelectorAll(
|
|
491
|
+
"script, style, iframe, object, embed, link",
|
|
492
|
+
).forEach((n) => n.remove());
|
|
436
493
|
|
|
437
494
|
tmp.querySelectorAll("*").forEach((n) => {
|
|
438
495
|
for (const a of Array.from(n.attributes)) {
|
|
439
496
|
const name = a.name ?? "";
|
|
440
497
|
const value = a.value ?? "";
|
|
441
|
-
|
|
498
|
+
|
|
442
499
|
if (/^on/i.test(name)) {
|
|
443
500
|
n.removeAttribute(name);
|
|
444
501
|
return;
|
|
445
502
|
}
|
|
446
|
-
|
|
447
|
-
if (
|
|
503
|
+
|
|
504
|
+
if (
|
|
505
|
+
/^(href|src|xlink:href)$/i.test(name) &&
|
|
506
|
+
/^javascript:/i.test(value)
|
|
507
|
+
) {
|
|
448
508
|
n.removeAttribute(name);
|
|
449
509
|
}
|
|
450
510
|
}
|
|
@@ -476,7 +536,10 @@ export class Libs {
|
|
|
476
536
|
*/
|
|
477
537
|
public static string2normalize(str: unknown): string {
|
|
478
538
|
if (str == null) return "";
|
|
479
|
-
const s = String(str)
|
|
539
|
+
const s = String(str)
|
|
540
|
+
.toLowerCase()
|
|
541
|
+
.normalize("NFD")
|
|
542
|
+
.replace(/[\u0300-\u036f]/g, "");
|
|
480
543
|
return s.replace(/đ/g, "d").replace(/Đ/g, "d");
|
|
481
544
|
}
|
|
482
545
|
|
|
@@ -491,7 +554,9 @@ export class Libs {
|
|
|
491
554
|
* @param {HTMLSelectElement} selectElement - The source select element.
|
|
492
555
|
* @returns {Array<HTMLOptGroupElement | HTMLOptionElement>} Flattened node list.
|
|
493
556
|
*/
|
|
494
|
-
public static parseSelectToArray(
|
|
557
|
+
public static parseSelectToArray(
|
|
558
|
+
selectElement: HTMLSelectElement,
|
|
559
|
+
): Array<HTMLOptGroupElement | HTMLOptionElement> {
|
|
495
560
|
const result: Array<HTMLOptGroupElement | HTMLOptionElement> = [];
|
|
496
561
|
const children = selectElement.children;
|
|
497
562
|
|
|
@@ -501,15 +566,19 @@ export class Libs {
|
|
|
501
566
|
const group = child as HTMLOptGroupElement;
|
|
502
567
|
result.push(group);
|
|
503
568
|
|
|
504
|
-
for (
|
|
569
|
+
for (
|
|
570
|
+
let optionIndex = 0;
|
|
571
|
+
optionIndex < group.children.length;
|
|
572
|
+
optionIndex++
|
|
573
|
+
) {
|
|
505
574
|
const option = group.children[optionIndex];
|
|
506
575
|
option["__parentGroup"] = group;
|
|
507
576
|
result.push(option as HTMLOptionElement);
|
|
508
|
-
}
|
|
577
|
+
}
|
|
509
578
|
} else if (child.tagName === "OPTION") {
|
|
510
579
|
result.push(child as HTMLOptionElement);
|
|
511
580
|
}
|
|
512
|
-
}
|
|
581
|
+
}
|
|
513
582
|
|
|
514
583
|
return result;
|
|
515
584
|
}
|
|
@@ -522,7 +591,10 @@ export class Libs {
|
|
|
522
591
|
*/
|
|
523
592
|
public static IsIOS(): boolean {
|
|
524
593
|
const ua = navigator.userAgent;
|
|
525
|
-
return
|
|
594
|
+
return (
|
|
595
|
+
/iP(hone|ad|od)/.test(ua) ||
|
|
596
|
+
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)
|
|
597
|
+
);
|
|
526
598
|
}
|
|
527
599
|
|
|
528
600
|
/**
|
|
@@ -535,18 +607,25 @@ export class Libs {
|
|
|
535
607
|
public static any2px(value: string): string {
|
|
536
608
|
const v = String(value).trim();
|
|
537
609
|
if (v.endsWith("px")) return v;
|
|
538
|
-
if (v.endsWith("vh"))
|
|
539
|
-
|
|
610
|
+
if (v.endsWith("vh"))
|
|
611
|
+
return (window.innerHeight * parseFloat(v)) / 100 + "px";
|
|
612
|
+
if (v.endsWith("vw"))
|
|
613
|
+
return (window.innerWidth * parseFloat(v)) / 100 + "px";
|
|
540
614
|
|
|
541
615
|
// rem/em: use computed font-size of document root
|
|
542
|
-
const fs = parseFloat(
|
|
616
|
+
const fs = parseFloat(
|
|
617
|
+
getComputedStyle(document.documentElement).fontSize,
|
|
618
|
+
);
|
|
543
619
|
if (v.endsWith("rem")) return fs * parseFloat(v) + "px";
|
|
544
620
|
|
|
545
621
|
// fallback: DOM measure
|
|
546
|
-
const el = this.nodeCreator({
|
|
622
|
+
const el = this.nodeCreator({
|
|
623
|
+
node: "div",
|
|
624
|
+
style: { height: v, opacity: "0" },
|
|
625
|
+
});
|
|
547
626
|
document.body.appendChild(el);
|
|
548
627
|
const px = el.offsetHeight + "px";
|
|
549
628
|
el.remove();
|
|
550
629
|
return px;
|
|
551
630
|
}
|
|
552
|
-
}
|
|
631
|
+
}
|