@devwareng/vanilla-ts 1.0.1 → 1.0.3
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/index.cjs +509 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +458 -0
- package/dist/index.mjs +458 -0
- package/package.json +7 -5
package/dist/index.js
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
// src/define/html.ts
|
|
2
|
+
function html(strings, ...values) {
|
|
3
|
+
return strings.reduce((result, str, i) => {
|
|
4
|
+
return result + str + (values[i] || "");
|
|
5
|
+
}, "");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/hooks/useTSPurifier.ts
|
|
9
|
+
import DOMPurify from "dompurify";
|
|
10
|
+
var useTSPurifier = (input, config) => {
|
|
11
|
+
const defaultConfig = {
|
|
12
|
+
ADD_TAGS: ["my-custom-tag"]
|
|
13
|
+
};
|
|
14
|
+
const mergedConfig = { ...defaultConfig, ...config };
|
|
15
|
+
if (typeof input === "string") {
|
|
16
|
+
return DOMPurify.sanitize(input, mergedConfig);
|
|
17
|
+
} else {
|
|
18
|
+
return DOMPurify.sanitize(input.innerHTML, mergedConfig);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/hooks/useTSEvent.ts
|
|
23
|
+
var useTSEvent = (id, eventType, handler) => {
|
|
24
|
+
const element = document.querySelector(`#${id}`);
|
|
25
|
+
if (element) {
|
|
26
|
+
element.addEventListener(
|
|
27
|
+
eventType,
|
|
28
|
+
handler
|
|
29
|
+
);
|
|
30
|
+
} else {
|
|
31
|
+
console.warn(`Element with id '${id}' not found.`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/hooks/useTSParams.ts
|
|
36
|
+
import { createStore } from "zustand/vanilla";
|
|
37
|
+
import DOMPurify2 from "dompurify";
|
|
38
|
+
function extractPatternParams(pattern, path) {
|
|
39
|
+
const paramNames = [];
|
|
40
|
+
const regexPattern = pattern.replace(/:[^/]+/g, (match2) => {
|
|
41
|
+
paramNames.push(match2.slice(1));
|
|
42
|
+
return "([^/]+)";
|
|
43
|
+
});
|
|
44
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
45
|
+
const match = path.match(regex);
|
|
46
|
+
const result = {};
|
|
47
|
+
if (match) {
|
|
48
|
+
paramNames.forEach((name, i) => {
|
|
49
|
+
result[name] = DOMPurify2.sanitize(match[i + 1] ?? "");
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
function extractQueryParams(search) {
|
|
55
|
+
const result = {};
|
|
56
|
+
const urlSearchParams = new URLSearchParams(search);
|
|
57
|
+
for (const [key, value] of urlSearchParams.entries()) {
|
|
58
|
+
result[key] = DOMPurify2.sanitize(value);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
var useTSParams = createStore((set, get) => ({
|
|
63
|
+
params: {},
|
|
64
|
+
query: {},
|
|
65
|
+
setFromPattern: (pattern) => {
|
|
66
|
+
const path = window.location.pathname;
|
|
67
|
+
const params = extractPatternParams(pattern, path);
|
|
68
|
+
const query = extractQueryParams(window.location.search);
|
|
69
|
+
set({ params, query });
|
|
70
|
+
},
|
|
71
|
+
getParam: (key) => get().params[key],
|
|
72
|
+
getQuery: (key) => get().query[key]
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
// src/hooks/useTSExtract.ts
|
|
76
|
+
function useTSExtractParams(pattern) {
|
|
77
|
+
const store = useTSParams.getState();
|
|
78
|
+
store.setFromPattern(pattern);
|
|
79
|
+
const params = store.params;
|
|
80
|
+
const query = store.query;
|
|
81
|
+
return { ...params, ...query };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/hooks/useTSAllElements.ts
|
|
85
|
+
var useTSEventAll = (selector, eventType, handler) => {
|
|
86
|
+
const elements = document.querySelectorAll(selector);
|
|
87
|
+
elements.forEach((element) => {
|
|
88
|
+
element.addEventListener(eventType, handler);
|
|
89
|
+
});
|
|
90
|
+
return () => {
|
|
91
|
+
elements.forEach((element) => {
|
|
92
|
+
element.removeEventListener(eventType, handler);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/hooks/useTSElements.ts
|
|
98
|
+
import DOMPurify3 from "dompurify";
|
|
99
|
+
var useTSElements = (htmlElement, element, config) => {
|
|
100
|
+
const defaultConfig = {
|
|
101
|
+
ALLOWED_TAGS: ["main", "div", "h1", "p", "button", "span", "a", "img", "input"],
|
|
102
|
+
ALLOWED_ATTR: ["class", "id", "href", "style", "src", "alt"],
|
|
103
|
+
...config
|
|
104
|
+
// allow user overrides
|
|
105
|
+
};
|
|
106
|
+
const sanitizedContent = DOMPurify3.sanitize(
|
|
107
|
+
/*html*/
|
|
108
|
+
element,
|
|
109
|
+
defaultConfig
|
|
110
|
+
);
|
|
111
|
+
if (htmlElement.innerHTML !== String(sanitizedContent)) {
|
|
112
|
+
return htmlElement.innerHTML = String(sanitizedContent);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/hooks/useIntialDOM.ts
|
|
117
|
+
import DOMPurify4 from "dompurify";
|
|
118
|
+
var previousHTML = null;
|
|
119
|
+
var useInitialDOM = (id, mount) => {
|
|
120
|
+
if (typeof document === "undefined") return;
|
|
121
|
+
const targetElement = document.getElementById(id);
|
|
122
|
+
if (!targetElement) return;
|
|
123
|
+
const currentHTML = targetElement.innerHTML;
|
|
124
|
+
const sanitizedHTML = DOMPurify4.sanitize(currentHTML);
|
|
125
|
+
if (previousHTML !== null && sanitizedHTML !== previousHTML) {
|
|
126
|
+
const fallbackEl = document.createElement("div");
|
|
127
|
+
mount(fallbackEl);
|
|
128
|
+
targetElement.innerHTML = previousHTML;
|
|
129
|
+
} else {
|
|
130
|
+
previousHTML = sanitizedHTML;
|
|
131
|
+
mount(targetElement);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// src/hooks/useTSAnchorSingle.ts
|
|
136
|
+
var sanitizeInput = (input) => {
|
|
137
|
+
const element = document.createElement("div");
|
|
138
|
+
element.innerText = input;
|
|
139
|
+
return element.innerHTML;
|
|
140
|
+
};
|
|
141
|
+
var useAnchorSingle = (element, href, ariaLabel, className = "", childElement = null) => {
|
|
142
|
+
if (!element) return;
|
|
143
|
+
const sanitizedHref = sanitizeInput(href);
|
|
144
|
+
const sanitizedAriaLabel = sanitizeInput(ariaLabel);
|
|
145
|
+
const sanitizedClassName = className ? sanitizeInput(className) : void 0;
|
|
146
|
+
element.setAttribute("href", sanitizedHref);
|
|
147
|
+
element.setAttribute("aria-label", sanitizedAriaLabel);
|
|
148
|
+
if (sanitizedClassName) {
|
|
149
|
+
element.className = sanitizedClassName;
|
|
150
|
+
}
|
|
151
|
+
if (childElement) {
|
|
152
|
+
element.innerHTML = "";
|
|
153
|
+
element.appendChild(childElement);
|
|
154
|
+
}
|
|
155
|
+
element.addEventListener("click", (e) => {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
const target = e.currentTarget;
|
|
158
|
+
const href2 = target.getAttribute("href");
|
|
159
|
+
if (href2) {
|
|
160
|
+
const scrollPosition = window.scrollY;
|
|
161
|
+
window.scrollTo(0, 0);
|
|
162
|
+
window.history.pushState({ scrollPosition }, "", href2);
|
|
163
|
+
const navEvent = new PopStateEvent("popstate");
|
|
164
|
+
dispatchEvent(navEvent);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
window.addEventListener("popstate", (e) => {
|
|
168
|
+
const state = e.state;
|
|
169
|
+
if (state && state.scrollPosition !== void 0) {
|
|
170
|
+
window.scrollTo(0, state.scrollPosition);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/hooks/useTSAnchor.ts
|
|
176
|
+
import debounce from "lodash";
|
|
177
|
+
var sanitizeInput2 = (input) => input;
|
|
178
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
179
|
+
sanitizeInput2 = (input) => {
|
|
180
|
+
const element = document.createElement("div");
|
|
181
|
+
element.innerText = input;
|
|
182
|
+
return element.innerHTML;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
var useAnchor = typeof window !== "undefined" ? debounce((anchors) => {
|
|
186
|
+
anchors.forEach((anchor) => {
|
|
187
|
+
if (!anchor) return;
|
|
188
|
+
const originalHref = anchor.getAttribute("href") || "#";
|
|
189
|
+
const originalClassName = anchor.getAttribute("class") || "";
|
|
190
|
+
const sanitizedHref = sanitizeInput2(originalHref);
|
|
191
|
+
const sanitizedClassName = anchor.getAttribute("class") ? sanitizeInput2(anchor.getAttribute("class")) : originalClassName;
|
|
192
|
+
anchor.setAttribute("href", sanitizedHref);
|
|
193
|
+
anchor.setAttribute("class", sanitizedClassName);
|
|
194
|
+
if (anchor.getAttribute("aria-label")) {
|
|
195
|
+
anchor.setAttribute(
|
|
196
|
+
"aria-label",
|
|
197
|
+
sanitizeInput2(anchor.getAttribute("aria-label"))
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const childElement = anchor.querySelector(":scope > *");
|
|
201
|
+
if (childElement) {
|
|
202
|
+
anchor.innerHTML = "";
|
|
203
|
+
anchor.appendChild(childElement);
|
|
204
|
+
}
|
|
205
|
+
anchor.addEventListener("click", (e) => {
|
|
206
|
+
const target = e.currentTarget;
|
|
207
|
+
const href = target.getAttribute("href");
|
|
208
|
+
try {
|
|
209
|
+
const url = new URL(href, window.location.href);
|
|
210
|
+
if (url.origin !== window.location.origin) return;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("Invalid URL:", error);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
window.history.pushState({}, "", href);
|
|
217
|
+
const navEvent = new PopStateEvent("popstate");
|
|
218
|
+
window.dispatchEvent(navEvent);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}) : () => {
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/hooks/useTSMetaData.ts
|
|
225
|
+
import DOMPurify5 from "dompurify";
|
|
226
|
+
|
|
227
|
+
// src/hooks/useTSComponent.ts
|
|
228
|
+
import DOMPurify6 from "dompurify";
|
|
229
|
+
var useTSComponent = (id, DOM, element, params, params2) => {
|
|
230
|
+
DOMPurify6.sanitize(DOM);
|
|
231
|
+
element(DOM.querySelector(`#${id}`), params, params2);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/hooks/useTSSelect.ts
|
|
235
|
+
var useTSSelect = (selector) => {
|
|
236
|
+
const element = document.querySelector(selector);
|
|
237
|
+
if (!element) {
|
|
238
|
+
console.warn(`[useTSSelect] No element found for selector: '${selector}'`);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return element;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/hooks/useTSAuth.ts
|
|
245
|
+
import { jwtDecode } from "jwt-decode";
|
|
246
|
+
var useTSAuth = (_Component, loginUrl) => {
|
|
247
|
+
const token = localStorage.getItem("token");
|
|
248
|
+
if (!token) {
|
|
249
|
+
window.location.href = loginUrl;
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const decodedToken = jwtDecode(token);
|
|
254
|
+
const currentTime = Date.now() / 1e3;
|
|
255
|
+
if (decodedToken.exp && decodedToken.exp < currentTime) {
|
|
256
|
+
console.error("Token has expired");
|
|
257
|
+
window.localStorage.removeItem("token");
|
|
258
|
+
window.location.href = loginUrl;
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error("Invalid token:", error);
|
|
264
|
+
window.location.href = loginUrl;
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// src/hooks/useTSForEach.ts
|
|
270
|
+
var useTSElementEach = (elements, events, callback) => {
|
|
271
|
+
elements.forEach((element) => {
|
|
272
|
+
events.forEach((eventType) => {
|
|
273
|
+
element.addEventListener(eventType, (event) => {
|
|
274
|
+
callback(element, event);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/routes/class/Router.class.ts
|
|
281
|
+
import DOMPurify8 from "dompurify";
|
|
282
|
+
|
|
283
|
+
// src/store/useTSParam.store.ts
|
|
284
|
+
import { createStore as createStore2 } from "zustand/vanilla";
|
|
285
|
+
import DOMPurify7 from "dompurify";
|
|
286
|
+
var tsParamsStore = createStore2((set) => ({
|
|
287
|
+
params: {},
|
|
288
|
+
query: {},
|
|
289
|
+
setParams: (params) => set(() => ({
|
|
290
|
+
params: sanitize(params)
|
|
291
|
+
})),
|
|
292
|
+
setQuery: (query) => set(() => ({
|
|
293
|
+
query: sanitize(query)
|
|
294
|
+
}))
|
|
295
|
+
}));
|
|
296
|
+
function sanitize(obj) {
|
|
297
|
+
const output = {};
|
|
298
|
+
for (const key in obj) {
|
|
299
|
+
output[key] = DOMPurify7.sanitize(obj[key]);
|
|
300
|
+
}
|
|
301
|
+
return output;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/routes/class/Router.class.ts
|
|
305
|
+
var TSRouter = class {
|
|
306
|
+
constructor(routes, expectedParams) {
|
|
307
|
+
this.routes = [];
|
|
308
|
+
this.routes = routes;
|
|
309
|
+
this.expectedParams = new Set(expectedParams);
|
|
310
|
+
window.addEventListener("popstate", this.handlePopState.bind(this));
|
|
311
|
+
this.handlePopState();
|
|
312
|
+
}
|
|
313
|
+
handlePopState() {
|
|
314
|
+
const currentPath = window.location.pathname;
|
|
315
|
+
const currentSearch = window.location.search;
|
|
316
|
+
const queryParams = this.parseQueryParams(currentSearch);
|
|
317
|
+
const matchingRoute = this.findMatchingRoute(currentPath, this.routes);
|
|
318
|
+
if (matchingRoute) {
|
|
319
|
+
if (matchingRoute.routeto) {
|
|
320
|
+
this.navigate(matchingRoute.routeto);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const sanitizedParams = this.filterAndSanitizeParams(matchingRoute.params);
|
|
324
|
+
tsParamsStore.getState().setParams(sanitizedParams);
|
|
325
|
+
tsParamsStore.getState().setQuery(queryParams);
|
|
326
|
+
const errorElement = document.createElement("div");
|
|
327
|
+
matchingRoute.element?.(errorElement, sanitizedParams, queryParams);
|
|
328
|
+
if (matchingRoute.children) {
|
|
329
|
+
const nestedPath = currentPath.slice(matchingRoute.path.length);
|
|
330
|
+
const childElement = errorElement.querySelector("#child");
|
|
331
|
+
if (childElement) {
|
|
332
|
+
this.renderChildren(
|
|
333
|
+
matchingRoute.children,
|
|
334
|
+
nestedPath,
|
|
335
|
+
childElement,
|
|
336
|
+
sanitizedParams,
|
|
337
|
+
queryParams
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
const notFoundRoute = this.findMatchingRoute("*", this.routes);
|
|
343
|
+
if (notFoundRoute) {
|
|
344
|
+
const fallbackParams = this.filterAndSanitizeParams(notFoundRoute.params);
|
|
345
|
+
tsParamsStore.getState().setParams(fallbackParams);
|
|
346
|
+
tsParamsStore.getState().setQuery(queryParams);
|
|
347
|
+
const errorElement = document.createElement("div");
|
|
348
|
+
notFoundRoute.element?.(errorElement, fallbackParams, queryParams);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
renderChildren(children, nestedPath, parentElement, parentParams, queryParams) {
|
|
353
|
+
if (!children || children.length === 0) {
|
|
354
|
+
const childElement = parentElement.querySelector("#child");
|
|
355
|
+
if (childElement) childElement.remove();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const matchingChild = this.findMatchingRoute(nestedPath, children);
|
|
359
|
+
if (matchingChild) {
|
|
360
|
+
const childElement = document.createElement("div");
|
|
361
|
+
childElement.id = "child";
|
|
362
|
+
const mergedParams = { ...parentParams, ...matchingChild.params };
|
|
363
|
+
const sanitizedParams = this.filterAndSanitizeParams(mergedParams);
|
|
364
|
+
tsParamsStore.getState().setParams(sanitizedParams);
|
|
365
|
+
tsParamsStore.getState().setQuery(queryParams);
|
|
366
|
+
matchingChild.element?.(childElement, sanitizedParams, queryParams);
|
|
367
|
+
parentElement.appendChild(childElement);
|
|
368
|
+
if (matchingChild.children) {
|
|
369
|
+
const nextNestedPath = nestedPath.slice(matchingChild.path.length);
|
|
370
|
+
this.renderChildren(
|
|
371
|
+
matchingChild.children,
|
|
372
|
+
nextNestedPath,
|
|
373
|
+
childElement,
|
|
374
|
+
sanitizedParams,
|
|
375
|
+
queryParams
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
parseQueryParams(search) {
|
|
381
|
+
const queryParams = {};
|
|
382
|
+
const urlSearchParams = new URLSearchParams(search);
|
|
383
|
+
for (const [key, value] of urlSearchParams.entries()) {
|
|
384
|
+
if (this.expectedParams.has(key)) {
|
|
385
|
+
queryParams[key] = DOMPurify8.sanitize(value);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return queryParams;
|
|
389
|
+
}
|
|
390
|
+
findMatchingRoute(path, routes, inheritedParams = {}) {
|
|
391
|
+
for (const route of routes) {
|
|
392
|
+
const routePath = route.path;
|
|
393
|
+
const isDefaultRoute = routePath === "*";
|
|
394
|
+
if (!isDefaultRoute) {
|
|
395
|
+
const paramNames = [];
|
|
396
|
+
const regexPattern = routePath.replace(/:[^\s/]+/g, (match2) => {
|
|
397
|
+
paramNames.push(match2.substring(1));
|
|
398
|
+
return "([^\\s/]+)";
|
|
399
|
+
});
|
|
400
|
+
const regex = new RegExp(`^${regexPattern}(?:/|$)`);
|
|
401
|
+
const match = path.match(regex);
|
|
402
|
+
if (match) {
|
|
403
|
+
const params = { ...inheritedParams };
|
|
404
|
+
paramNames.forEach((name, index) => {
|
|
405
|
+
params[name] = match[index + 1] ?? "";
|
|
406
|
+
});
|
|
407
|
+
if (route.children) {
|
|
408
|
+
const nestedPath = path.slice(match[0].length);
|
|
409
|
+
const matchingChild = this.findMatchingRoute(
|
|
410
|
+
nestedPath,
|
|
411
|
+
route.children,
|
|
412
|
+
params
|
|
413
|
+
);
|
|
414
|
+
if (matchingChild) return matchingChild;
|
|
415
|
+
}
|
|
416
|
+
return { ...route, params };
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
return route;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
424
|
+
filterAndSanitizeParams(params) {
|
|
425
|
+
if (!params) return {};
|
|
426
|
+
const sanitizedParams = {};
|
|
427
|
+
for (const key in params) {
|
|
428
|
+
if (this.expectedParams.has(key)) {
|
|
429
|
+
sanitizedParams[key] = DOMPurify8.sanitize(params[key] ?? "");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return sanitizedParams;
|
|
433
|
+
}
|
|
434
|
+
navigate(path) {
|
|
435
|
+
history.pushState(null, "", path);
|
|
436
|
+
this.handlePopState();
|
|
437
|
+
}
|
|
438
|
+
addRoute(route) {
|
|
439
|
+
this.routes.push(route);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
export {
|
|
443
|
+
TSRouter,
|
|
444
|
+
html,
|
|
445
|
+
useAnchor,
|
|
446
|
+
useAnchorSingle,
|
|
447
|
+
useInitialDOM,
|
|
448
|
+
useTSAuth,
|
|
449
|
+
useTSComponent,
|
|
450
|
+
useTSElementEach,
|
|
451
|
+
useTSElements,
|
|
452
|
+
useTSEvent,
|
|
453
|
+
useTSEventAll,
|
|
454
|
+
useTSExtractParams,
|
|
455
|
+
useTSParams,
|
|
456
|
+
useTSPurifier,
|
|
457
|
+
useTSSelect
|
|
458
|
+
};
|