@xtia/jel 0.1.0
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 +49 -0
- package/index.d.ts +50 -0
- package/index.js +242 -0
- package/package.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Jel
|
|
2
|
+
## Or, How I Learned To Stop Worrying And Love The DOM
|
|
3
|
+
### TypeScript Edition
|
|
4
|
+
|
|
5
|
+
Jel is a thin layer over the DOM to simplify element structure creation, manipulation and componentisation with 'vanilla' TS.
|
|
6
|
+
|
|
7
|
+
See [demo/index.ts](demo/index.ts) for example operation. Compare with [resulting page](https://aleta.codes/jel-ts-demo/).
|
|
8
|
+
|
|
9
|
+
## `$` basic use:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
$ npm i @xtia/jel
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`$.[tagname](details)` produces an element of `<tagname>`. `details` can be content of various types or a descriptor object.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { $ } from "jel-ts";
|
|
19
|
+
|
|
20
|
+
body.append($.form([
|
|
21
|
+
$.h2("Sign in"),
|
|
22
|
+
$.label("Email"),
|
|
23
|
+
$.input({ attribs: { name: "email" }}),
|
|
24
|
+
$.label("Password"),
|
|
25
|
+
$.input({ attribs: { name: "password", type: "password" }}),
|
|
26
|
+
$.button("Sign in"),
|
|
27
|
+
$.a({
|
|
28
|
+
content: ["Having trouble? ", $.strong("Recover account")],
|
|
29
|
+
attribs: {
|
|
30
|
+
href: "/recover-account",
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
]));
|
|
34
|
+
|
|
35
|
+
body.append([
|
|
36
|
+
$.h2("Files"),
|
|
37
|
+
$.ul(
|
|
38
|
+
files.map(file => $.li(
|
|
39
|
+
$.a({
|
|
40
|
+
content: file.name,
|
|
41
|
+
attribs: {
|
|
42
|
+
href: `/files/${file.name}`,
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
))
|
|
46
|
+
)
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type ElementClassSpec = string | Record<string, boolean> | ElementClassSpec[];
|
|
2
|
+
export type DOMContent = number | null | string | Element | JelEntity<object, any> | Text | DOMContent[];
|
|
3
|
+
export type DomEntity<T extends HTMLElement> = JelEntity<ElementAPI<T>, HTMLElementEventMap>;
|
|
4
|
+
type Classes = string | Record<string, boolean> | Classes[];
|
|
5
|
+
type PartFn<Spec, API extends {}, EventDataMap> = (spec?: Partial<Spec & CommonOptions<EventDataMap>>) => JelEntity<API, EventDataMap>;
|
|
6
|
+
type CommonOptions<EventDataMap> = {
|
|
7
|
+
on?: Partial<{
|
|
8
|
+
[EventID in keyof EventDataMap]: (data: EventDataMap[EventID]) => void;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
type JelEntity<API extends {}, EventDataMap> = API & {
|
|
12
|
+
on<E extends keyof EventDataMap>(eventId: E, handler: (this: JelEntity<API, EventDataMap>, data: EventDataMap[E]) => void): void;
|
|
13
|
+
readonly [componentDataSymbol]: JelComponentData;
|
|
14
|
+
};
|
|
15
|
+
interface ElementDescriptor {
|
|
16
|
+
classes?: Classes;
|
|
17
|
+
content?: DOMContent;
|
|
18
|
+
attribs?: Record<string, string | number | boolean>;
|
|
19
|
+
on?: Partial<{
|
|
20
|
+
[E in keyof HTMLElementEventMap]: (event: HTMLElementEventMap[E]) => void;
|
|
21
|
+
}>;
|
|
22
|
+
style?: Partial<{
|
|
23
|
+
[key in keyof CSSStyleDeclaration]: string | number;
|
|
24
|
+
}> & Record<string, string | number>;
|
|
25
|
+
}
|
|
26
|
+
type DomHelper = ((<T extends keyof HTMLElementTagNameMap>(tagName: T, descriptor: ElementDescriptor) => HTMLElementTagNameMap[T]) & ((selector: string, content?: DOMContent) => DomEntity<any>) & (<T extends HTMLElement>(element: T) => DomEntity<T>) & {
|
|
27
|
+
[T in keyof HTMLElementTagNameMap]: (descriptor: ElementDescriptor) => DomEntity<HTMLElementTagNameMap[T]>;
|
|
28
|
+
} & {
|
|
29
|
+
[T in keyof HTMLElementTagNameMap]: (content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]>;
|
|
30
|
+
});
|
|
31
|
+
type JelComponentData = {
|
|
32
|
+
dom: DOMContent;
|
|
33
|
+
};
|
|
34
|
+
export declare const $: DomHelper;
|
|
35
|
+
declare const componentDataSymbol: unique symbol;
|
|
36
|
+
type ElementAPI<T extends HTMLElement> = {
|
|
37
|
+
element: T;
|
|
38
|
+
content: DOMContent;
|
|
39
|
+
classes: DOMTokenList;
|
|
40
|
+
attribs: {
|
|
41
|
+
[key: string]: string | null;
|
|
42
|
+
};
|
|
43
|
+
style: CSSStyleDeclaration;
|
|
44
|
+
innerHTML: string;
|
|
45
|
+
qsa(selector: string): DomEntity<any>[];
|
|
46
|
+
append(...content: DOMContent[]): void;
|
|
47
|
+
remove(): void;
|
|
48
|
+
};
|
|
49
|
+
export declare function definePart<Spec, API extends object, EventDataMap extends Record<string, any> = {}>(defaultOptions: Spec, init: (spec: Spec, append: (content: DOMContent) => void, trigger: <K extends keyof EventDataMap>(eventId: K, eventData: EventDataMap[K]) => void) => API | void): PartFn<Spec, API, EventDataMap>;
|
|
50
|
+
export {};
|
package/index.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.$ = void 0;
|
|
15
|
+
exports.definePart = definePart;
|
|
16
|
+
function createElement(tag, descriptor) {
|
|
17
|
+
if (descriptor === void 0) { descriptor = {}; }
|
|
18
|
+
if (isContent(descriptor))
|
|
19
|
+
descriptor = { content: descriptor };
|
|
20
|
+
var ent = getWrappedElement(document.createElement(tag));
|
|
21
|
+
var applyClasses = function (classes) {
|
|
22
|
+
if (Array.isArray(classes))
|
|
23
|
+
return classes.forEach(function (c) { return applyClasses(c); });
|
|
24
|
+
if (typeof classes == "string") {
|
|
25
|
+
classes.trim().split(/\s+/).forEach(function (c) { return ent.classes.add(c); });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
Object.entries(classes).forEach(function (_a) {
|
|
29
|
+
var className = _a[0], state = _a[1];
|
|
30
|
+
if (state)
|
|
31
|
+
applyClasses(className);
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
applyClasses(descriptor.classes || []);
|
|
35
|
+
if (descriptor.attribs)
|
|
36
|
+
Object.entries(descriptor.attribs).forEach(function (_a) {
|
|
37
|
+
var k = _a[0], v = _a[1];
|
|
38
|
+
if (v === false) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
ent.element.setAttribute(k, v === true ? k : v);
|
|
42
|
+
});
|
|
43
|
+
var addContent = function (content) {
|
|
44
|
+
if (Array.isArray(content))
|
|
45
|
+
return content.forEach(function (c) { return addContent(c); });
|
|
46
|
+
if (content)
|
|
47
|
+
ent.append(content);
|
|
48
|
+
};
|
|
49
|
+
if (descriptor.content)
|
|
50
|
+
addContent(descriptor.content);
|
|
51
|
+
descriptor.style && Object.entries(descriptor.style).forEach(function (_a) {
|
|
52
|
+
var prop = _a[0], val = _a[1];
|
|
53
|
+
if (/\-/.test(prop)) {
|
|
54
|
+
ent.element.style.setProperty(prop, val.toString());
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
ent.element.style[prop] = val;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
descriptor.on && Object.entries(descriptor.on).forEach(function (_a) {
|
|
61
|
+
var eventName = _a[0], handler = _a[1];
|
|
62
|
+
return ent.on(eventName, handler);
|
|
63
|
+
});
|
|
64
|
+
return ent;
|
|
65
|
+
}
|
|
66
|
+
;
|
|
67
|
+
var isContent = function (value) {
|
|
68
|
+
return ["string", "number"].includes(typeof value)
|
|
69
|
+
|| value instanceof Element
|
|
70
|
+
|| value instanceof Text
|
|
71
|
+
|| Array.isArray(value)
|
|
72
|
+
|| !value;
|
|
73
|
+
};
|
|
74
|
+
exports.$ = new Proxy(createElement, {
|
|
75
|
+
apply: function (create, _0, _a) {
|
|
76
|
+
var _b;
|
|
77
|
+
var selectorOrTagName = _a[0], contentOrDescriptor = _a[1];
|
|
78
|
+
if (selectorOrTagName instanceof HTMLElement)
|
|
79
|
+
return getWrappedElement(selectorOrTagName);
|
|
80
|
+
var tagName = ((_b = selectorOrTagName.match(/^[^.#]*/)) === null || _b === void 0 ? void 0 : _b[0]) || "";
|
|
81
|
+
if (!tagName)
|
|
82
|
+
throw new Error("Invalid tag");
|
|
83
|
+
var matches = selectorOrTagName.slice(tagName.length).match(/[.#][^.#]+/g);
|
|
84
|
+
var classes = {};
|
|
85
|
+
var descriptor = {
|
|
86
|
+
classes: classes,
|
|
87
|
+
content: contentOrDescriptor,
|
|
88
|
+
};
|
|
89
|
+
matches === null || matches === void 0 ? void 0 : matches.forEach(function (m) {
|
|
90
|
+
var value = m.slice(1);
|
|
91
|
+
if (m[0] == ".") {
|
|
92
|
+
classes[value] = true;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
descriptor.attribs = { id: value };
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return create(tagName, descriptor);
|
|
99
|
+
},
|
|
100
|
+
get: function (create, tagName) {
|
|
101
|
+
return function (descriptorOrContent) {
|
|
102
|
+
return create(tagName, descriptorOrContent);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
var componentDataSymbol = Symbol("jelComponentData");
|
|
107
|
+
var elementWrapCache = new WeakMap();
|
|
108
|
+
function definePart(defaultOptions, init) {
|
|
109
|
+
return (function (partialSpec) {
|
|
110
|
+
var _a, _b;
|
|
111
|
+
if (partialSpec === void 0) { partialSpec = {}; }
|
|
112
|
+
var spec = __assign(__assign({}, defaultOptions), partialSpec);
|
|
113
|
+
var eventHandlers = {};
|
|
114
|
+
var addEventListener = function (eventId, fn) {
|
|
115
|
+
if (!eventHandlers[eventId])
|
|
116
|
+
eventHandlers[eventId] = [];
|
|
117
|
+
eventHandlers[eventId].push(fn);
|
|
118
|
+
};
|
|
119
|
+
if (spec.on)
|
|
120
|
+
Object.entries(spec.on).forEach(function (_a) {
|
|
121
|
+
var eventId = _a[0], handler = _a[1];
|
|
122
|
+
addEventListener(eventId, handler);
|
|
123
|
+
});
|
|
124
|
+
var entity;
|
|
125
|
+
var content = [];
|
|
126
|
+
var append = function (c) {
|
|
127
|
+
if (entity)
|
|
128
|
+
throw new Error("Component root content can only be added during initialisation");
|
|
129
|
+
content.push(c);
|
|
130
|
+
};
|
|
131
|
+
var trigger = function (eventId, data) {
|
|
132
|
+
var _a;
|
|
133
|
+
(_a = eventHandlers[eventId]) === null || _a === void 0 ? void 0 : _a.forEach(function (fn) { return fn.call(entity, data); });
|
|
134
|
+
};
|
|
135
|
+
var api = init(spec, append, trigger);
|
|
136
|
+
entity = api ? Object.create(api, (_a = {},
|
|
137
|
+
_a[componentDataSymbol] = {
|
|
138
|
+
value: {
|
|
139
|
+
dom: content
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
_a)) : (_b = {},
|
|
143
|
+
_b[componentDataSymbol] = {
|
|
144
|
+
dom: content
|
|
145
|
+
},
|
|
146
|
+
_b);
|
|
147
|
+
return entity;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
;
|
|
151
|
+
var attribsProxy = {
|
|
152
|
+
get: function (element, key) {
|
|
153
|
+
return element.getAttribute(key);
|
|
154
|
+
},
|
|
155
|
+
set: function (element, key, value) {
|
|
156
|
+
element.setAttribute(key, value);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
function getWrappedElement(element) {
|
|
161
|
+
var _a;
|
|
162
|
+
if (!elementWrapCache.has(element)) {
|
|
163
|
+
var recursiveAppend_1 = function (c) {
|
|
164
|
+
if (c === undefined)
|
|
165
|
+
debugger;
|
|
166
|
+
if (c === null)
|
|
167
|
+
return;
|
|
168
|
+
if (Array.isArray(c)) {
|
|
169
|
+
c.forEach(function (item) { return recursiveAppend_1(item); });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (isJelEntity(c)) {
|
|
173
|
+
recursiveAppend_1(c[componentDataSymbol].dom);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (typeof c == "number")
|
|
177
|
+
c = c.toString();
|
|
178
|
+
element.append(c);
|
|
179
|
+
};
|
|
180
|
+
var domEntity_1 = (_a = {},
|
|
181
|
+
_a[componentDataSymbol] = {
|
|
182
|
+
dom: element,
|
|
183
|
+
},
|
|
184
|
+
Object.defineProperty(_a, "element", {
|
|
185
|
+
get: function () { return element; },
|
|
186
|
+
enumerable: false,
|
|
187
|
+
configurable: true
|
|
188
|
+
}),
|
|
189
|
+
_a.on = function (eventId, handler) {
|
|
190
|
+
element.addEventListener(eventId, function (eventData) {
|
|
191
|
+
handler.call(domEntity_1, eventData);
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
_a.append = function () {
|
|
195
|
+
var content = [];
|
|
196
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
197
|
+
content[_i] = arguments[_i];
|
|
198
|
+
}
|
|
199
|
+
recursiveAppend_1(content);
|
|
200
|
+
},
|
|
201
|
+
_a.remove = function () {
|
|
202
|
+
element.remove();
|
|
203
|
+
},
|
|
204
|
+
_a.classes = element.classList,
|
|
205
|
+
_a.qsa = function (selector) {
|
|
206
|
+
return [].slice.call(element.querySelectorAll(selector)).map(function (el) { return getWrappedElement(el); });
|
|
207
|
+
},
|
|
208
|
+
Object.defineProperty(_a, "content", {
|
|
209
|
+
get: function () {
|
|
210
|
+
return [].slice.call(element.children).map(function (child) {
|
|
211
|
+
if (child instanceof HTMLElement)
|
|
212
|
+
return getWrappedElement(child);
|
|
213
|
+
return child;
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
set: function (v) {
|
|
217
|
+
element.innerHTML = "";
|
|
218
|
+
recursiveAppend_1(v);
|
|
219
|
+
},
|
|
220
|
+
enumerable: false,
|
|
221
|
+
configurable: true
|
|
222
|
+
}),
|
|
223
|
+
_a.attribs = new Proxy(element, attribsProxy),
|
|
224
|
+
Object.defineProperty(_a, "innerHTML", {
|
|
225
|
+
get: function () {
|
|
226
|
+
return element.innerHTML;
|
|
227
|
+
},
|
|
228
|
+
set: function (v) {
|
|
229
|
+
element.innerHTML = v;
|
|
230
|
+
},
|
|
231
|
+
enumerable: false,
|
|
232
|
+
configurable: true
|
|
233
|
+
}),
|
|
234
|
+
_a.style = element.style,
|
|
235
|
+
_a);
|
|
236
|
+
elementWrapCache.set(element, domEntity_1);
|
|
237
|
+
}
|
|
238
|
+
return elementWrapCache.get(element);
|
|
239
|
+
}
|
|
240
|
+
function isJelEntity(content) {
|
|
241
|
+
return typeof content == "object" && !!content && componentDataSymbol in content;
|
|
242
|
+
}
|