@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.
Files changed (4) hide show
  1. package/README.md +49 -0
  2. package/index.d.ts +50 -0
  3. package/index.js +242 -0
  4. 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
+ }
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@xtia/jel",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight DOM wrapper",
5
+ "main": "index.js",
6
+ "keywords": [
7
+ "dom"
8
+ ],
9
+ "author": "Aleta Lovelace",
10
+ "license": "MIT"
11
+ }