Skip to content

Commit c1e5779

Browse files
committed
Add createElement helpers.
1 parent 5d528d5 commit c1e5779

File tree

7 files changed

+357
-33
lines changed

7 files changed

+357
-33
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
CHANGELOG
22
=========
33

4-
Next release
5-
------------
4+
v1.0.9
5+
------
66

77
* Add support `URL` instance for `request` helper.
8+
* Add `createElement` helper.
89

910
v1.0.8
1011
------

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fivelab/web-utils",
3-
"version": "1.0.8",
3+
"version": "1.0.9",
44
"private": false,
55
"type": "module",
66
"description": "The helpers for easy manipulate with dom.",

src/browser/assets.ts

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createElement } from '../dom';
2+
13
export type AppendScriptOptions = {
24
type?: string;
35
async?: boolean;
@@ -53,22 +55,13 @@ export function appendScript(src: string, options: AppendScriptOptions = {}): Pr
5355
}
5456

5557
const promise: Promise<HTMLScriptElement> = new Promise((resolve, reject) => {
56-
const script = document.createElement('script');
57-
58-
script.src = src;
59-
script.type = options.type ?? 'text/javascript';
60-
61-
if (options.async !== undefined) {
62-
script.async = options.async;
63-
}
64-
65-
if (options.defer !== undefined) {
66-
script.defer = options.defer;
67-
}
68-
69-
if (options.attributes) {
70-
Object.entries(options.attributes).forEach(([k, v]) => script.setAttribute(k, v));
71-
}
58+
const script = createElement('script', {
59+
src: src,
60+
type: options.type ?? 'text/javascript',
61+
async: options.async,
62+
defer: options.defer,
63+
attrs: options.attributes,
64+
});
7265

7366
const cleanup = () => {
7467
script.removeEventListener('load', onLoad);
@@ -119,18 +112,12 @@ export function appendStyle(href: string, options: AppendStyleOptions = {}): Pro
119112
}
120113

121114
const promise = new Promise<HTMLLinkElement>((resolve, reject) => {
122-
const link = document.createElement('link');
123-
124-
link.rel = 'stylesheet';
125-
link.href = href;
126-
127-
if (options.media) {
128-
link.media = options.media;
129-
}
130-
131-
if (options.attributes) {
132-
Object.entries(options.attributes).forEach(([k, v]) => link.setAttribute(k, v));
133-
}
115+
const link = createElement('link', {
116+
href: href,
117+
rel: 'stylesheet',
118+
media: options.media,
119+
attrs: options.attributes,
120+
});
134121

135122
const onLoad = () => {
136123
resolve(link);

src/dom/create-element.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
type AttributeValue = string | number | boolean | null | undefined;
2+
3+
type ContentOptions =
4+
| { text?: string; html?: never }
5+
| { html?: string; text?: never }
6+
| { text?: undefined; html?: undefined };
7+
8+
export type CommonOptions = {
9+
id?: string;
10+
dataset?: Record<string, AttributeValue>;
11+
attrs?: Record<string, AttributeValue>;
12+
};
13+
14+
export type BaseOptions = CommonOptions & {
15+
classList?: string[];
16+
style?: Partial<CSSStyleDeclaration>;
17+
} & ContentOptions;
18+
19+
export type AnchorOptions = BaseOptions & {
20+
href?: URL | string;
21+
target?: '_blank' | '_self' | '_parent' | '_top' | (string & {});
22+
}
23+
24+
export type ScriptOptions = CommonOptions & {
25+
src?: URL | string;
26+
type?: 'module' | 'importmap' | 'text/javascript' | (string & {});
27+
async?: boolean;
28+
defer?: boolean;
29+
};
30+
31+
export type LinkOptions = CommonOptions & {
32+
as?: string;
33+
rel?: 'stylesheet' | (string & {});
34+
href?: URL | string;
35+
type?: string;
36+
media?: string;
37+
title?: string;
38+
}
39+
40+
type TagsWithCustomOptions = 'a' | 'script' | 'link';
41+
42+
function normalizeAttributeValue(value: AttributeValue, dataset = false): string | null {
43+
if (typeof value === 'undefined' || null === value) {
44+
return null;
45+
}
46+
47+
if (typeof value === 'boolean') {
48+
return value ? (dataset ? 'true' : '1') : (dataset ? 'false' : '0');
49+
}
50+
51+
return String(value);
52+
}
53+
54+
function applyAnchorOptions(el: HTMLAnchorElement, options: AnchorOptions): void {
55+
if (options.href !== undefined) {
56+
el.href = options.href instanceof URL ? options.href.href : options.href;
57+
}
58+
59+
if (options.target !== undefined) {
60+
el.target = options.target;
61+
}
62+
}
63+
64+
function applyScriptOptions(el: HTMLScriptElement, options: ScriptOptions): void {
65+
if (options.src) {
66+
el.src = options.src instanceof URL ? options.src.href : options.src;
67+
}
68+
69+
if (options.type !== undefined) {
70+
el.type = options.type;
71+
}
72+
73+
if (options.async !== undefined) {
74+
el.async = options.async;
75+
}
76+
77+
if (options.defer !== undefined) {
78+
el.defer = options.defer;
79+
}
80+
}
81+
82+
function applyLinkOptions(el: HTMLLinkElement, options: LinkOptions): void {
83+
if (options.as !== undefined) {
84+
el.as = options.as;
85+
}
86+
87+
if (options.rel !== undefined) {
88+
el.rel = options.rel;
89+
}
90+
91+
if (options.href !== undefined) {
92+
el.href = options.href instanceof URL ? options.href.href : options.href;
93+
}
94+
95+
if (options.type !== undefined) {
96+
el.type = options.type;
97+
}
98+
99+
if (options.media !== undefined) {
100+
el.media = options.media;
101+
}
102+
103+
if (options.title !== undefined) {
104+
el.title = options.title;
105+
}
106+
}
107+
108+
export function createElement(tag: 'a', options: AnchorOptions): HTMLAnchorElement;
109+
export function createElement(tag: 'script', options: ScriptOptions): HTMLScriptElement;
110+
export function createElement(tag: 'link', options: LinkOptions): HTMLLinkElement;
111+
export function createElement<K extends Exclude<keyof HTMLElementTagNameMap, TagsWithCustomOptions>>(tag: K, options?: BaseOptions): HTMLElementTagNameMap[K];
112+
113+
export function createElement(tag: string, options: BaseOptions = {}): HTMLElement {
114+
const el = document.createElement(tag);
115+
116+
if (options.id) {
117+
el.id = options.id;
118+
}
119+
120+
if (options.classList && options.classList.length) {
121+
el.classList.add(...options.classList);
122+
}
123+
124+
if (options.dataset) {
125+
Object.entries(options.dataset).forEach(([k, v]) => {
126+
v = normalizeAttributeValue(v, true);
127+
128+
if (null !== v) {
129+
(el.dataset)[k] = v;
130+
}
131+
});
132+
}
133+
134+
if (options.attrs) {
135+
Object.entries(options.attrs).forEach(([k, v]) => {
136+
v = normalizeAttributeValue(v, false);
137+
138+
if (null !== v) {
139+
el.setAttribute(k, v);
140+
}
141+
});
142+
}
143+
144+
if (options.text !== undefined) {
145+
el.textContent = options.text;
146+
}
147+
148+
if (options.html !== undefined) {
149+
el.innerHTML = options.html;
150+
}
151+
152+
if (options.style) {
153+
Object.assign(el.style, options.style);
154+
}
155+
156+
if ('a' === tag) {
157+
applyAnchorOptions(el as HTMLAnchorElement, options as AnchorOptions);
158+
}
159+
160+
if ('script' === tag) {
161+
applyScriptOptions(el as HTMLScriptElement, options as ScriptOptions);
162+
}
163+
164+
if ('link' === tag) {
165+
applyLinkOptions(el as HTMLLinkElement, options as LinkOptions);
166+
}
167+
168+
return el;
169+
}

src/dom/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './attributes';
22
export * from './changes';
33
export * from './classes';
4+
export * from './create-element';
45
export * from './events';
56
export * from './form';
67
export * from './html';

0 commit comments

Comments
 (0)