@xtia/jel 0.4.5 → 0.5.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 CHANGED
@@ -1,9 +1,9 @@
1
1
  # Jel
2
2
  ### Or, How I Learned To Stop Worrying And Love The DOM
3
3
 
4
- Jel is a thin layer over the DOM to simplify element structure creation, manipulation and componentisation with 'vanilla' TS.
4
+ Jel is a thin layer over the DOM to simplify element structure creation, manipulation and componentisation with 'vanilla' TS/JS.
5
5
 
6
- See [demo/index.ts](https://github.com/tiadrop/jel-ts/blob/main/demo/index.ts) for example operation. Compare with [resulting page](https://aleta.codes/jel-ts-demo/).
6
+ See [demo/index.ts](https://github.com/tiadrop/jel-ts/blob/main/demo/index.ts) for reusable components. Compare with [resulting page](https://aleta.codes/jel-ts-demo/).
7
7
 
8
8
  ## `$` Basic Use:
9
9
 
@@ -47,7 +47,7 @@ body.append([
47
47
 
48
48
  ## `DOMContent`
49
49
 
50
- Content can be string, Text, HTMLElement, JelEntity or arbitrarily nested array of content. Typing as DOMContent where possible enables flexibility.
50
+ Content can be string, Text, HTMLElement, JelEntity or arbitrarily nested array of content. Typing as DOMContent carries that flexibility to your own interfaces.
51
51
 
52
52
  ```ts
53
53
  function showDialogue(content: DOMContent) => {
@@ -124,4 +124,17 @@ If you need an element with just a class, id and/or content you can use `tag#id.
124
124
 
125
125
  ```ts
126
126
  showDialogue(["Hello ", $("span.green", "world")]);
127
- ```
127
+ ```
128
+
129
+ ## Event composition
130
+
131
+ Event emitters can be chained:
132
+
133
+ ```ts
134
+ element.events.mousemove
135
+ .takeUntil(body.events.mousedown.filter(e => e.button === 1))
136
+ .map(ev => [ev.offsetX, ev.offsetY])
137
+ .apply(([x, y]) => console.log("mouse @ ", x, y));
138
+ ```
139
+
140
+ For RxJS users, events can be observed with `fromEvent(element.events, "mousemove")`.
package/index.d.ts CHANGED
@@ -1,108 +1,4 @@
1
- export type ElementClassDescriptor = string | Record<string, boolean> | ElementClassDescriptor[];
2
- export type DOMContent = number | null | string | Element | JelEntity<object> | Text | DOMContent[];
3
- export type DomEntity<T extends HTMLElement> = JelEntity<ElementAPI<T>>;
4
- type PartConstructor<Spec, API extends object | void, EventDataMap> = (spec: Spec & EventSpec<EventDataMap>) => JelEntity<EventHost<API, EventDataMap>>;
5
- type EventSpec<EventDataMap> = EventDataMap extends object ? {
6
- on?: {
7
- [EventID in keyof EventDataMap]+?: (data: EventDataMap[EventID]) => void;
8
- };
9
- } : {};
10
- type JelEntity<API extends object | void> = (API extends void ? {} : API) & {
11
- readonly [entityDataSymbol]: JelEntityData;
12
- };
13
- type CSSValue = string | number | null | HexCodeContainer;
14
- type HexCodeContainer = {
15
- hexCode: string;
16
- toString(): string;
17
- };
18
- type StylesDescriptor = {
19
- [K in keyof CSSStyleDeclaration as [
20
- K,
21
- CSSStyleDeclaration[K]
22
- ] extends [string, string] ? K : never]+?: CSSValue;
23
- };
24
- type StyleAccessor = StylesDescriptor & ((styles: StylesDescriptor) => void) & ((property: keyof StylesDescriptor, value: CSSValue) => void);
25
- type ContentlessTag = "area" | "br" | "hr" | "iframe" | "input" | "textarea" | "img" | "canvas" | "link" | "meta";
26
- type TagWithHref = "a" | "link";
27
- type TagWithSrc = "img" | "script" | "iframe" | "video" | "audio";
28
- type TagWithValue = "input" | "textarea";
29
- type ContentlessElement = HTMLElementTagNameMap[ContentlessTag];
30
- type ElementDescriptor<Tag extends string> = {
31
- classes?: ElementClassDescriptor;
32
- attribs?: Record<string, string | number | boolean>;
33
- on?: {
34
- [E in keyof HTMLElementEventMap]+?: (event: HTMLElementEventMap[E]) => void;
35
- };
36
- style?: StylesDescriptor;
37
- cssVariables?: Record<string, CSSValue>;
38
- } & (Tag extends TagWithValue ? {
39
- value?: string | number;
40
- } : {}) & (Tag extends ContentlessTag ? {} : {
41
- content?: DOMContent;
42
- }) & (Tag extends TagWithSrc ? {
43
- src?: string;
44
- } : {}) & (Tag extends TagWithHref ? {
45
- href?: string;
46
- } : {}) & (Tag extends "canvas" ? {
47
- width: number;
48
- height: number;
49
- } : {});
50
- type ElementAPI<T extends HTMLElement> = EventHost<{
51
- readonly element: T;
52
- classes: DOMTokenList;
53
- attribs: {
54
- [key: string]: string | null;
55
- };
56
- style: StyleAccessor;
57
- setCSSVariable(table: Record<string, CSSValue>): void;
58
- setCSSVariable(variableName: string, value: CSSValue): void;
59
- qsa(selector: string): (Element | DomEntity<HTMLElement>)[];
60
- remove(): void;
61
- getRect(): DOMRect;
62
- focus(): void;
63
- blur(): void;
64
- } & (T extends ContentlessElement ? {} : {
65
- append(...content: DOMContent[]): void;
66
- innerHTML: string;
67
- content: DOMContent;
68
- }) & (T extends HTMLElementTagNameMap[TagWithValue] ? {
69
- value: string;
70
- select(): void;
71
- } : {}) & (T extends HTMLCanvasElement ? {
72
- width: number;
73
- height: number;
74
- getContext: HTMLCanvasElement["getContext"];
75
- } : {}) & (T extends HTMLElementTagNameMap[TagWithSrc] ? {
76
- src: string;
77
- } : {}) & (T extends HTMLElementTagNameMap[TagWithHref] ? {
78
- href: string;
79
- } : {}) & (T extends HTMLMediaElement ? {
80
- play(): void;
81
- pause(): void;
82
- currentTime: number;
83
- readonly paused: boolean;
84
- } : {}), HTMLElementEventMap>;
85
- type DomHelper = ((<T extends keyof HTMLElementTagNameMap>(tagName: T, descriptor: ElementDescriptor<T>) => DomEntity<HTMLElementTagNameMap[T]>) & (<T extends keyof HTMLElementTagNameMap>(selector: `${T}#${string}`, content?: T extends ContentlessTag ? void : DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & (<T extends keyof HTMLElementTagNameMap>(selector: `${T}.${string}`, content?: T extends ContentlessTag ? void : DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & (<T extends keyof HTMLElementTagNameMap>(selector: T, content?: T extends ContentlessTag ? void : DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & (<T extends HTMLElement>(element: T) => DomEntity<T>) & {
86
- [T in keyof HTMLElementTagNameMap]: (descriptor: ElementDescriptor<T>) => DomEntity<HTMLElementTagNameMap[T]>;
87
- } & {
88
- [T in keyof HTMLElementTagNameMap]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]>;
89
- });
90
- type JelEntityData = {
91
- dom: DOMContent;
92
- };
93
- type OptionalKeys<T> = {
94
- [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
95
- }[keyof T];
96
- type Optionals<T> = {
97
- [K in OptionalKeys<T>]-?: Exclude<T[K], undefined>;
98
- };
99
- type ForbidKey<K extends string | symbol> = Record<string | symbol, any> & Partial<Record<K, never>>;
100
- type EventHost<API extends object | void, EventDataMap> = (API extends object ? API : {}) & {
101
- on<E extends keyof EventDataMap>(eventId: E, handler: (this: JelEntity<EventHost<API, EventDataMap>>, data: EventDataMap[E]) => void): void;
102
- };
103
- export declare const $: DomHelper;
104
- declare const entityDataSymbol: unique symbol;
105
- export declare function createEntity<API extends object>(content: DOMContent, api: API extends DOMContent ? never : API): JelEntity<API>;
106
- export declare function createEntity(content: DOMContent): JelEntity<void>;
107
- export declare function definePart<Spec extends ForbidKey<"on">, API extends ForbidKey<"on"> | void = void, EventDataMap extends Record<string, any> = {}>(defaultOptions: Optionals<Spec>, init: (spec: Required<Spec>, append: (...content: DOMContent[]) => void, trigger: <K extends keyof EventDataMap>(eventId: K, eventData: EventDataMap[K]) => void) => API): PartConstructor<Spec, API, EventDataMap>;
108
- export {};
1
+ export { DomEntity, ElementClassDescriptor, ElementDescriptor, DOMContent, DomHelper, StyleAccessor, JelEntity } from "./internal/types";
2
+ export { $ } from "./internal/element";
3
+ export { createEntity } from "./internal/util";
4
+ export { createEventSource } from "./internal/emitter";
package/index.js CHANGED
@@ -1,308 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.$ = void 0;
4
- exports.createEntity = createEntity;
5
- exports.definePart = definePart;
6
- const styleProxy = {
7
- get(getStyle, prop) {
8
- return getStyle()[prop];
9
- },
10
- set(getStyle, prop, value) {
11
- getStyle()[prop] = value;
12
- return true;
13
- },
14
- apply(getStyle, _, [stylesOrProp, value]) {
15
- const style = getStyle();
16
- if (typeof stylesOrProp == "object") {
17
- Object.entries(stylesOrProp).forEach(([prop, val]) => style[prop] = val);
18
- return;
19
- }
20
- style[stylesOrProp] = value;
21
- },
22
- deleteProperty(getStyle, prop) {
23
- getStyle()[prop] = null;
24
- return true;
25
- }
26
- };
27
- function createElement(tag, descriptor = {}) {
28
- if (isContent(descriptor))
29
- return createElement(tag, {
30
- content: descriptor,
31
- });
32
- const domElement = document.createElement(tag);
33
- const ent = getWrappedElement(domElement);
34
- const applyClasses = (classes) => {
35
- if (Array.isArray(classes)) {
36
- return classes.forEach(c => applyClasses(c));
37
- }
38
- if (typeof classes == "string") {
39
- classes.trim().split(/\s+/).forEach(c => ent.classes.add(c));
40
- return;
41
- }
42
- Object.entries(classes).forEach(([className, state]) => {
43
- if (state)
44
- applyClasses(className);
45
- });
46
- };
47
- applyClasses(descriptor.classes || []);
48
- ["value", "src", "href", "width", "height"].forEach(prop => {
49
- if (descriptor[prop] !== undefined)
50
- domElement.setAttribute(prop, descriptor[prop]);
51
- });
52
- // attribs.value / attribs.src / attribs.href override descriptor.*
53
- if (descriptor.attribs) {
54
- Object.entries(descriptor.attribs).forEach(([k, v]) => {
55
- if (v === false) {
56
- return;
57
- }
58
- domElement.setAttribute(k, v === true ? k : v);
59
- });
60
- }
61
- if (descriptor.content !== undefined)
62
- recursiveAppend(domElement, descriptor.content);
63
- if (descriptor.style) {
64
- ent.style(descriptor.style);
65
- }
66
- if (descriptor.cssVariables) {
67
- ent.setCSSVariable(descriptor.cssVariables);
68
- }
69
- if (descriptor.on) {
70
- Object.entries(descriptor.on).forEach(([eventName, handler]) => ent.on(eventName, handler));
71
- }
72
- return ent;
73
- }
74
- ;
75
- exports.$ = new Proxy(createElement, {
76
- apply(create, _, [selectorOrTagName, contentOrDescriptor]) {
77
- var _a;
78
- if (selectorOrTagName instanceof HTMLElement)
79
- return getWrappedElement(selectorOrTagName);
80
- const tagName = ((_a = selectorOrTagName.match(/^[^.#]*/)) === null || _a === void 0 ? void 0 : _a[0]) || "";
81
- if (!tagName)
82
- throw new Error("Invalid tag");
83
- const matches = selectorOrTagName.slice(tagName.length).match(/[.#][^.#]+/g);
84
- const classes = {};
85
- const descriptor = {
86
- classes,
87
- content: contentOrDescriptor,
88
- };
89
- matches === null || matches === void 0 ? void 0 : matches.forEach((m) => {
90
- const 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(create, tagName) {
101
- return (descriptorOrContent) => {
102
- return create(tagName, descriptorOrContent);
103
- };
104
- }
105
- });
106
- const entityDataSymbol = Symbol("jelComponentData");
107
- const elementWrapCache = new WeakMap();
108
- const attribsProxy = {
109
- get: (element, key) => {
110
- return element.getAttribute(key);
111
- },
112
- set: (element, key, value) => {
113
- element.setAttribute(key, value);
114
- return true;
115
- },
116
- has: (element, key) => {
117
- return element.hasAttribute(key);
118
- },
119
- ownKeys: (element) => {
120
- return element.getAttributeNames();
121
- },
122
- };
123
- const recursiveAppend = (parent, c) => {
124
- if (c === null)
125
- return;
126
- if (Array.isArray(c)) {
127
- c.forEach(item => recursiveAppend(parent, item));
128
- return;
129
- }
130
- if (isJelEntity(c)) {
131
- recursiveAppend(parent, c[entityDataSymbol].dom);
132
- return;
133
- }
134
- if (typeof c == "number")
135
- c = c.toString();
136
- parent.append(c);
137
- };
138
- function getWrappedElement(element) {
139
- if (!elementWrapCache.has(element)) {
140
- const setCSSVariable = (k, v) => {
141
- if (v === null) {
142
- element.style.removeProperty("--" + k);
143
- }
144
- else {
145
- element.style.setProperty("--" + k, v);
146
- }
147
- };
148
- const domEntity = {
149
- [entityDataSymbol]: {
150
- dom: element,
151
- },
152
- get element() { return element; },
153
- on(eventId, handler) {
154
- element.addEventListener(eventId, eventData => {
155
- handler.call(domEntity, eventData);
156
- });
157
- },
158
- append(...content) {
159
- recursiveAppend(element, content);
160
- },
161
- remove: () => element.remove(),
162
- setCSSVariable(variableNameOrTable, value) {
163
- if (typeof variableNameOrTable == "object") {
164
- Object.entries(variableNameOrTable).forEach(([k, v]) => setCSSVariable(k, v));
165
- return;
166
- }
167
- setCSSVariable(variableNameOrTable, value);
168
- },
169
- qsa(selector) {
170
- const results = [];
171
- element.querySelectorAll(selector).forEach((el) => results.push(el instanceof HTMLElement ? getWrappedElement(el) : el));
172
- return results;
173
- },
174
- getRect: () => element.getBoundingClientRect(),
175
- focus: () => element.focus(),
176
- blur: () => element.blur(),
177
- select: () => element.select(),
178
- play: () => element.play(),
179
- pause: () => element.pause(),
180
- getContext(mode, options) {
181
- return element.getContext(mode, options);
182
- },
183
- get content() {
184
- return [].slice.call(element.children).map((child) => {
185
- if (child instanceof HTMLElement)
186
- return getWrappedElement(child);
187
- return child;
188
- });
189
- },
190
- set content(v) {
191
- element.innerHTML = "";
192
- recursiveAppend(element, v);
193
- },
194
- attribs: new Proxy(element, attribsProxy),
195
- get innerHTML() {
196
- return element.innerHTML;
197
- },
198
- set innerHTML(v) {
199
- element.innerHTML = v;
200
- },
201
- get value() {
202
- return element.value;
203
- },
204
- set value(v) {
205
- element.value = v;
206
- },
207
- get href() {
208
- return element.href;
209
- },
210
- set href(v) {
211
- element.href = v;
212
- },
213
- get src() {
214
- return element.src;
215
- },
216
- set src(v) {
217
- element.src = v;
218
- },
219
- get width() {
220
- return element.width;
221
- },
222
- set width(v) {
223
- element.width = v;
224
- },
225
- get height() {
226
- return element.height;
227
- },
228
- set height(v) {
229
- element.height = v;
230
- },
231
- get currentTime() {
232
- return element.currentTime;
233
- },
234
- set currentTime(v) {
235
- element.currentTime = v;
236
- },
237
- get paused() {
238
- return element.paused;
239
- },
240
- style: new Proxy(() => element.style, styleProxy),
241
- classes: element.classList,
242
- };
243
- elementWrapCache.set(element, domEntity);
244
- }
245
- return elementWrapCache.get(element);
246
- }
247
- const isContent = (value) => {
248
- if (value === undefined)
249
- return false;
250
- return typeof value == "string"
251
- || typeof value == "number"
252
- || !value
253
- || value instanceof Element
254
- || value instanceof Text
255
- || entityDataSymbol in value
256
- || Array.isArray(value);
257
- };
258
- function isJelEntity(content) {
259
- return typeof content == "object" && !!content && entityDataSymbol in content;
260
- }
261
- function createEntity(content, api) {
262
- if (isContent(api)) {
263
- throw new TypeError("API object is already valid content");
264
- }
265
- return Object.create(api !== null && api !== void 0 ? api : {}, {
266
- [entityDataSymbol]: {
267
- value: {
268
- dom: content
269
- }
270
- },
271
- });
272
- }
273
- ;
274
- function definePart(defaultOptions, init) {
275
- return ((spec) => {
276
- var _a;
277
- const fullSpec = Object.assign(Object.assign({}, defaultOptions), spec);
278
- const eventHandlers = {};
279
- const addEventListener = (eventId, fn) => {
280
- if (!eventHandlers[eventId])
281
- eventHandlers[eventId] = [];
282
- eventHandlers[eventId].push(fn);
283
- };
284
- if (fullSpec.on)
285
- Object.entries(fullSpec.on).forEach(([eventId, handler]) => {
286
- addEventListener(eventId, handler);
287
- });
288
- let entity;
289
- const content = [];
290
- const append = (...c) => {
291
- if (entity)
292
- throw new Error("Component root content can only be added during initialisation");
293
- content.push(...c);
294
- };
295
- const trigger = (eventId, data) => {
296
- var _a;
297
- (_a = eventHandlers[eventId]) === null || _a === void 0 ? void 0 : _a.forEach(fn => fn.call(entity, data));
298
- };
299
- const api = (_a = init(fullSpec, append, trigger)) !== null && _a !== void 0 ? _a : {};
300
- entity = createEntity(content, Object.create(api, {
301
- on: {
302
- value: addEventListener,
303
- }
304
- }));
305
- return entity;
306
- });
307
- }
308
- ;
1
+ export { $ } from "./internal/element";
2
+ export { createEntity } from "./internal/util";
3
+ export { createEventSource } from "./internal/emitter";
@@ -0,0 +1,2 @@
1
+ import { DomHelper } from "./types";
2
+ export declare const $: DomHelper;
@@ -0,0 +1,220 @@
1
+ import { attribsProxy, eventsProxy, styleProxy } from "./proxy";
2
+ import { entityDataSymbol, isContent, isJelEntity } from "./util";
3
+ const elementWrapCache = new WeakMap();
4
+ const recursiveAppend = (parent, c) => {
5
+ if (c === null)
6
+ return;
7
+ if (Array.isArray(c)) {
8
+ c.forEach(item => recursiveAppend(parent, item));
9
+ return;
10
+ }
11
+ if (isJelEntity(c)) {
12
+ recursiveAppend(parent, c[entityDataSymbol].dom);
13
+ return;
14
+ }
15
+ if (typeof c == "number")
16
+ c = c.toString();
17
+ parent.append(c);
18
+ };
19
+ function createElement(tag, descriptor = {}) {
20
+ if (isContent(descriptor))
21
+ return createElement(tag, {
22
+ content: descriptor,
23
+ });
24
+ const domElement = document.createElement(tag);
25
+ const ent = getWrappedElement(domElement);
26
+ const applyClasses = (classes) => {
27
+ if (Array.isArray(classes)) {
28
+ return classes.forEach(c => applyClasses(c));
29
+ }
30
+ if (typeof classes == "string") {
31
+ classes.trim().split(/\s+/).forEach(c => ent.classes.add(c));
32
+ return;
33
+ }
34
+ if (classes === undefined)
35
+ return;
36
+ Object.entries(classes).forEach(([className, state]) => {
37
+ if (state)
38
+ applyClasses(className);
39
+ });
40
+ };
41
+ applyClasses(descriptor.classes || []);
42
+ ["value", "src", "href", "width", "height", "type", "name"].forEach(prop => {
43
+ if (descriptor[prop] !== undefined)
44
+ domElement.setAttribute(prop, descriptor[prop]);
45
+ });
46
+ // attribs.value / attribs.src / attribs.href override descriptor.*
47
+ if (descriptor.attribs) {
48
+ Object.entries(descriptor.attribs).forEach(([k, v]) => {
49
+ if (v === false) {
50
+ return;
51
+ }
52
+ domElement.setAttribute(k, v === true ? k : v);
53
+ });
54
+ }
55
+ if (descriptor.content !== undefined)
56
+ recursiveAppend(domElement, descriptor.content);
57
+ if (descriptor.style) {
58
+ ent.style(descriptor.style);
59
+ }
60
+ if (descriptor.cssVariables) {
61
+ ent.setCSSVariable(descriptor.cssVariables);
62
+ }
63
+ if (descriptor.on) {
64
+ Object.entries(descriptor.on).forEach(([eventName, handler]) => ent.events[eventName].apply(handler));
65
+ }
66
+ return ent;
67
+ }
68
+ ;
69
+ export const $ = new Proxy(createElement, {
70
+ apply(create, _, [selectorOrTagName, contentOrDescriptor]) {
71
+ var _a;
72
+ if (selectorOrTagName instanceof HTMLElement)
73
+ return getWrappedElement(selectorOrTagName);
74
+ const tagName = ((_a = selectorOrTagName.match(/^[^.#]*/)) === null || _a === void 0 ? void 0 : _a[0]) || "";
75
+ if (!tagName)
76
+ throw new Error("Invalid tag");
77
+ const matches = selectorOrTagName.slice(tagName.length).match(/[.#][^.#]+/g);
78
+ const classes = {};
79
+ const descriptor = {
80
+ classes,
81
+ content: contentOrDescriptor,
82
+ };
83
+ matches === null || matches === void 0 ? void 0 : matches.forEach((m) => {
84
+ const value = m.slice(1);
85
+ if (m[0] == ".") {
86
+ classes[value] = true;
87
+ }
88
+ else {
89
+ descriptor.attribs = { id: value };
90
+ }
91
+ });
92
+ return create(tagName, descriptor);
93
+ },
94
+ get(create, tagName) {
95
+ return (descriptorOrContent) => {
96
+ return create(tagName, descriptorOrContent);
97
+ };
98
+ }
99
+ });
100
+ function getWrappedElement(element) {
101
+ if (!elementWrapCache.has(element)) {
102
+ const setCSSVariable = (k, v) => {
103
+ if (v === null) {
104
+ element.style.removeProperty("--" + k);
105
+ }
106
+ else {
107
+ element.style.setProperty("--" + k, v);
108
+ }
109
+ };
110
+ const domEntity = {
111
+ [entityDataSymbol]: {
112
+ dom: element,
113
+ },
114
+ get element() { return element; },
115
+ on(eventId, handler) {
116
+ element.addEventListener(eventId, eventData => {
117
+ handler.call(domEntity, eventData);
118
+ });
119
+ },
120
+ append(...content) {
121
+ recursiveAppend(element, content);
122
+ },
123
+ remove: () => element.remove(),
124
+ setCSSVariable(variableNameOrTable, value) {
125
+ if (typeof variableNameOrTable == "object") {
126
+ Object.entries(variableNameOrTable).forEach(([k, v]) => setCSSVariable(k, v));
127
+ return;
128
+ }
129
+ setCSSVariable(variableNameOrTable, value);
130
+ },
131
+ qsa(selector) {
132
+ const results = [];
133
+ element.querySelectorAll(selector).forEach((el) => results.push(el instanceof HTMLElement ? getWrappedElement(el) : el));
134
+ return results;
135
+ },
136
+ getRect: () => element.getBoundingClientRect(),
137
+ focus: () => element.focus(),
138
+ blur: () => element.blur(),
139
+ select: () => element.select(),
140
+ play: () => element.play(),
141
+ pause: () => element.pause(),
142
+ getContext(mode, options) {
143
+ return element.getContext(mode, options);
144
+ },
145
+ get content() {
146
+ return [].slice.call(element.children).map((child) => {
147
+ if (child instanceof HTMLElement)
148
+ return getWrappedElement(child);
149
+ return child;
150
+ });
151
+ },
152
+ set content(v) {
153
+ element.innerHTML = "";
154
+ recursiveAppend(element, v);
155
+ },
156
+ attribs: new Proxy(element, attribsProxy),
157
+ get innerHTML() {
158
+ return element.innerHTML;
159
+ },
160
+ set innerHTML(v) {
161
+ element.innerHTML = v;
162
+ },
163
+ get value() {
164
+ return element.value;
165
+ },
166
+ set value(v) {
167
+ element.value = v;
168
+ },
169
+ get href() {
170
+ return element.href;
171
+ },
172
+ set href(v) {
173
+ element.href = v;
174
+ },
175
+ get src() {
176
+ return element.src;
177
+ },
178
+ set src(v) {
179
+ element.src = v;
180
+ },
181
+ get width() {
182
+ return element.width;
183
+ },
184
+ set width(v) {
185
+ element.width = v;
186
+ },
187
+ get height() {
188
+ return element.height;
189
+ },
190
+ set height(v) {
191
+ element.height = v;
192
+ },
193
+ get currentTime() {
194
+ return element.currentTime;
195
+ },
196
+ set currentTime(v) {
197
+ element.currentTime = v;
198
+ },
199
+ get paused() {
200
+ return element.paused;
201
+ },
202
+ get name() {
203
+ return element.getAttribute("name");
204
+ },
205
+ set name(v) {
206
+ if (v === null) {
207
+ element.removeAttribute("name");
208
+ }
209
+ else {
210
+ element.setAttribute("name", v);
211
+ }
212
+ },
213
+ style: new Proxy(() => element.style, styleProxy),
214
+ classes: element.classList,
215
+ events: new Proxy(element, eventsProxy)
216
+ };
217
+ elementWrapCache.set(element, domEntity);
218
+ }
219
+ return elementWrapCache.get(element);
220
+ }
@@ -0,0 +1,123 @@
1
+ type Handler<T> = (value: T) => void;
2
+ export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
3
+ export type UnsubscribeFunc = () => void;
4
+ export declare class Emitter<T> {
5
+ protected onListen: ListenFunc<T>;
6
+ protected constructor(onListen: ListenFunc<T>);
7
+ protected transform<R = T>(handler: (value: T, emit: (value: R) => void) => void): (fn: (v: R) => void) => UnsubscribeFunc;
8
+ /**
9
+ * Compatibility alias for `apply()` - registers a function to receive emitted values
10
+ * @param handler
11
+ * @returns A function to deregister the handler
12
+ */
13
+ listen(handler: Handler<T>): UnsubscribeFunc;
14
+ /**
15
+ * Registers a function to receive emitted values
16
+ * @param handler
17
+ * @returns A function to deregister the handler
18
+ */
19
+ apply(handler: Handler<T>): UnsubscribeFunc;
20
+ /**
21
+ * Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
22
+ * @param mapFunc
23
+ * @returns Listenable: emits transformed values
24
+ */
25
+ map<R>(mapFunc: (value: T) => R): Emitter<R>;
26
+ /**
27
+ * Creates a chainable emitter that selectively forwards emissions along the chain
28
+ * @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
29
+ * @returns Listenable: emits values that pass the filter
30
+ */
31
+ filter(check: (value: T) => boolean): Emitter<T>;
32
+ /**
33
+ * Creates a chainable emitter that discards emitted values that are the same as the last value emitted by the new emitter
34
+ * @param compare Optional function that takes the previous and next values and returns true if they should be considered equal
35
+ *
36
+ * If no `compare` function is provided, values will be compared via `===`
37
+ * @returns Listenable: emits non-repeating values
38
+ */
39
+ dedupe(compare?: (a: T, b: T) => boolean): Emitter<T>;
40
+ /**
41
+ * Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
42
+ *
43
+ * The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
44
+ * All listeners attached to the returned emitter receive the same values as the parent emitter.
45
+ *
46
+ * *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
47
+ *
48
+ * @param cb A function to be called as a side effect for each value emitted by the parent emitter.
49
+ * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
50
+ */
51
+ tap(cb: Handler<T>): Emitter<T>;
52
+ /**
53
+ * Immediately passes this emitter to a callback and returns this emitter
54
+ *
55
+ * Allows branching without breaking a composition chain
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * range
60
+ * .tween("0%", "100%")
61
+ * .fork(branch => branch
62
+ * .map(s => `Loading: ${s}`)
63
+ * .apply(s => document.title = s)
64
+ * )
65
+ * .apply(v => progressBar.style.width = v);
66
+ * ```
67
+ * @param cb
68
+ */
69
+ fork(cb: (branch: this) => void): this;
70
+ }
71
+ export declare class EventEmitter<T> extends Emitter<T> {
72
+ constructor(listen: ListenFunc<T>);
73
+ debounce(ms: number): EventEmitter<T>;
74
+ throttle(ms: number): EventEmitter<T>;
75
+ batch(ms: number): Emitter<T[]>;
76
+ filter(check: (value: T) => boolean): EventEmitter<T>;
77
+ once(): EventEmitter<T>;
78
+ scan<S>(updater: (state: S, value: T) => S, initial: S): EventEmitter<S>;
79
+ buffer(count: number): EventEmitter<T[]>;
80
+ take(limit: number): EventEmitter<T>;
81
+ tap(cb: Handler<T>): EventEmitter<T>;
82
+ takeUntil(notifier: Emitter<any>): Emitter<T>;
83
+ }
84
+ /**
85
+ * Creates a linked Emitter and emit() pair
86
+ * @example
87
+ * ```ts
88
+ * function createForm(options: { onsubmit?: (data: FormData) => void }) {
89
+ * const submitEvents = createEventSource(options.onsubmit);
90
+ * const form = $.form({
91
+ * on: {
92
+ * submit: (e) => {
93
+ * e.preventDefault();
94
+ * const data = new FormData(e.target);
95
+ * submitEvents.emit(data); // emit when form is submitted
96
+ * }
97
+ * }
98
+ * });
99
+ *
100
+ * return createEntity(form, {
101
+ * events: {
102
+ * submit: submitEvents.emitter
103
+ * }
104
+ * })
105
+ * }
106
+ *
107
+ * const form = createForm({
108
+ * onsubmit: (data) => handleSubmission(data)
109
+ * });
110
+ * ```
111
+ *
112
+ * @param initialHandler Optional listener automatically applied to the resulting Emitter
113
+ * @returns
114
+ */
115
+ export declare function createEventSource<T>(initialHandler?: Handler<T>): {
116
+ emit: (value: T) => void;
117
+ emitter: EventEmitter<T>;
118
+ };
119
+ export declare function createListenable<T>(onAddFirst?: () => void, onRemoveLast?: () => void): {
120
+ listen: (fn: (v: T) => void) => UnsubscribeFunc;
121
+ emit: (value: T) => void;
122
+ };
123
+ export {};
@@ -0,0 +1,279 @@
1
+ export class Emitter {
2
+ constructor(onListen) {
3
+ this.onListen = onListen;
4
+ }
5
+ transform(handler) {
6
+ let parentUnsubscribe = null;
7
+ const parentListen = this.onListen;
8
+ const { emit, listen } = createListenable(() => parentUnsubscribe = parentListen(value => {
9
+ handler(value, emit);
10
+ }), () => {
11
+ parentUnsubscribe();
12
+ parentUnsubscribe = null;
13
+ });
14
+ return listen;
15
+ }
16
+ /**
17
+ * Compatibility alias for `apply()` - registers a function to receive emitted values
18
+ * @param handler
19
+ * @returns A function to deregister the handler
20
+ */
21
+ listen(handler) {
22
+ return this.onListen(handler);
23
+ }
24
+ /**
25
+ * Registers a function to receive emitted values
26
+ * @param handler
27
+ * @returns A function to deregister the handler
28
+ */
29
+ apply(handler) {
30
+ return this.onListen(handler);
31
+ }
32
+ /**
33
+ * Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
34
+ * @param mapFunc
35
+ * @returns Listenable: emits transformed values
36
+ */
37
+ map(mapFunc) {
38
+ const listen = this.transform((value, emit) => emit(mapFunc(value)));
39
+ return new Emitter(listen);
40
+ }
41
+ /**
42
+ * Creates a chainable emitter that selectively forwards emissions along the chain
43
+ * @param check Function that takes an emitted value and returns true if the emission should be forwarded along the chain
44
+ * @returns Listenable: emits values that pass the filter
45
+ */
46
+ filter(check) {
47
+ const listen = this.transform((value, emit) => check(value) && emit(value));
48
+ return new Emitter(listen);
49
+ }
50
+ /**
51
+ * Creates a chainable emitter that discards emitted values that are the same as the last value emitted by the new emitter
52
+ * @param compare Optional function that takes the previous and next values and returns true if they should be considered equal
53
+ *
54
+ * If no `compare` function is provided, values will be compared via `===`
55
+ * @returns Listenable: emits non-repeating values
56
+ */
57
+ dedupe(compare) {
58
+ let previous = null;
59
+ const listen = this.transform((value, emit) => {
60
+ if (!previous || (compare
61
+ ? !compare(previous.value, value)
62
+ : (previous.value !== value))) {
63
+ emit(value);
64
+ previous = { value };
65
+ }
66
+ });
67
+ return new Emitter(listen);
68
+ }
69
+ /**
70
+ * Creates a chainable emitter that mirrors emissions from the parent emitter, invoking the provided callback `cb` as a side effect for each emission.
71
+ *
72
+ * The callback `cb` is called exactly once per parent emission, regardless of how many listeners are attached to the returned emitter.
73
+ * All listeners attached to the returned emitter receive the same values as the parent emitter.
74
+ *
75
+ * *Note*, the side effect `cb` is only invoked when there is at least one listener attached to the returned emitter
76
+ *
77
+ * @param cb A function to be called as a side effect for each value emitted by the parent emitter.
78
+ * @returns A new emitter that forwards all values from the parent, invoking `cb` as a side effect.
79
+ */
80
+ tap(cb) {
81
+ const listen = this.transform((value, emit) => {
82
+ cb(value);
83
+ emit(value);
84
+ });
85
+ return new Emitter(listen);
86
+ }
87
+ /**
88
+ * Immediately passes this emitter to a callback and returns this emitter
89
+ *
90
+ * Allows branching without breaking a composition chain
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * range
95
+ * .tween("0%", "100%")
96
+ * .fork(branch => branch
97
+ * .map(s => `Loading: ${s}`)
98
+ * .apply(s => document.title = s)
99
+ * )
100
+ * .apply(v => progressBar.style.width = v);
101
+ * ```
102
+ * @param cb
103
+ */
104
+ fork(cb) {
105
+ cb(this);
106
+ return this;
107
+ }
108
+ }
109
+ export class EventEmitter extends Emitter {
110
+ constructor(listen) {
111
+ super(listen);
112
+ }
113
+ debounce(ms) {
114
+ let reset = null;
115
+ const listen = this.transform((value, emit) => {
116
+ reset === null || reset === void 0 ? void 0 : reset();
117
+ const timeout = setTimeout(() => {
118
+ reset = null;
119
+ emit(value);
120
+ }, ms);
121
+ reset = () => {
122
+ reset = null;
123
+ clearTimeout(timeout);
124
+ };
125
+ });
126
+ return new EventEmitter(listen);
127
+ }
128
+ throttle(ms) {
129
+ let lastTime = 0;
130
+ const listen = this.transform((value, emit) => {
131
+ const now = Date.now();
132
+ if (now < lastTime + ms) {
133
+ lastTime = now;
134
+ emit(value);
135
+ }
136
+ });
137
+ return new EventEmitter(listen);
138
+ }
139
+ batch(ms) {
140
+ let items = [];
141
+ let active = false;
142
+ const listen = this.transform((value, emit) => {
143
+ items.push(value);
144
+ if (!active) {
145
+ active = true;
146
+ setTimeout(() => {
147
+ emit(items);
148
+ items = [];
149
+ active = false;
150
+ }, ms);
151
+ }
152
+ });
153
+ return new EventEmitter(listen);
154
+ }
155
+ filter(check) {
156
+ const listen = this.transform((value, emit) => check(value) && emit(value));
157
+ return new EventEmitter(listen);
158
+ }
159
+ once() {
160
+ const { emit, listen } = createListenable();
161
+ const unsub = this.apply(v => {
162
+ unsub();
163
+ emit(v);
164
+ });
165
+ return new EventEmitter(listen);
166
+ }
167
+ scan(updater, initial) {
168
+ let state = initial;
169
+ const listen = this.transform((value, emit) => {
170
+ state = updater(state, value);
171
+ emit(state);
172
+ });
173
+ return new EventEmitter(listen);
174
+ }
175
+ buffer(count) {
176
+ let buffer = [];
177
+ const listen = this.transform((value, emit) => {
178
+ buffer.push(value);
179
+ if (buffer.length >= count) {
180
+ emit(buffer);
181
+ buffer = [];
182
+ }
183
+ });
184
+ return new EventEmitter(listen);
185
+ }
186
+ take(limit) {
187
+ const { emit, listen } = createListenable();
188
+ let count = 0;
189
+ const unsub = this.apply(v => {
190
+ if (count < limit) {
191
+ emit(v);
192
+ count++;
193
+ if (count >= limit) {
194
+ unsub();
195
+ }
196
+ }
197
+ });
198
+ return new EventEmitter(listen);
199
+ }
200
+ tap(cb) {
201
+ const listen = this.transform((value, emit) => {
202
+ cb(value);
203
+ emit(value);
204
+ });
205
+ return new EventEmitter(listen);
206
+ }
207
+ takeUntil(notifier) {
208
+ const { emit, listen } = createListenable();
209
+ const unsub = this.apply(v => {
210
+ emit(v);
211
+ });
212
+ const unsubNotifier = notifier.apply(() => {
213
+ unsub();
214
+ unsubNotifier();
215
+ });
216
+ return new Emitter(listen);
217
+ }
218
+ }
219
+ /**
220
+ * Creates a linked Emitter and emit() pair
221
+ * @example
222
+ * ```ts
223
+ * function createForm(options: { onsubmit?: (data: FormData) => void }) {
224
+ * const submitEvents = createEventSource(options.onsubmit);
225
+ * const form = $.form({
226
+ * on: {
227
+ * submit: (e) => {
228
+ * e.preventDefault();
229
+ * const data = new FormData(e.target);
230
+ * submitEvents.emit(data); // emit when form is submitted
231
+ * }
232
+ * }
233
+ * });
234
+ *
235
+ * return createEntity(form, {
236
+ * events: {
237
+ * submit: submitEvents.emitter
238
+ * }
239
+ * })
240
+ * }
241
+ *
242
+ * const form = createForm({
243
+ * onsubmit: (data) => handleSubmission(data)
244
+ * });
245
+ * ```
246
+ *
247
+ * @param initialHandler Optional listener automatically applied to the resulting Emitter
248
+ * @returns
249
+ */
250
+ export function createEventSource(initialHandler) {
251
+ const { emit, listen } = createListenable();
252
+ if (initialHandler)
253
+ listen(initialHandler);
254
+ return {
255
+ emit,
256
+ emitter: new EventEmitter(listen)
257
+ };
258
+ }
259
+ export function createListenable(onAddFirst, onRemoveLast) {
260
+ const handlers = [];
261
+ const addListener = (fn) => {
262
+ const unique = { fn };
263
+ handlers.push(unique);
264
+ if (onAddFirst && handlers.length == 1)
265
+ onAddFirst();
266
+ return () => {
267
+ const idx = handlers.indexOf(unique);
268
+ if (idx === -1)
269
+ throw new Error("Handler already unsubscribed");
270
+ handlers.splice(idx, 1);
271
+ if (onRemoveLast && handlers.length == 0)
272
+ onRemoveLast();
273
+ };
274
+ };
275
+ return {
276
+ listen: addListener,
277
+ emit: (value) => handlers.forEach(h => h.fn(value)),
278
+ };
279
+ }
@@ -0,0 +1,3 @@
1
+ export declare const styleProxy: ProxyHandler<() => CSSStyleDeclaration>;
2
+ export declare const attribsProxy: ProxyHandler<HTMLElement>;
3
+ export declare const eventsProxy: ProxyHandler<HTMLElement>;
@@ -0,0 +1,55 @@
1
+ import { EventEmitter } from "./emitter";
2
+ export const styleProxy = {
3
+ get(getStyle, prop) {
4
+ return getStyle()[prop];
5
+ },
6
+ set(getStyle, prop, value) {
7
+ getStyle()[prop] = value;
8
+ return true;
9
+ },
10
+ apply(getStyle, _, [stylesOrProp, value]) {
11
+ const style = getStyle();
12
+ if (typeof stylesOrProp == "object") {
13
+ Object.entries(stylesOrProp).forEach(([prop, val]) => style[prop] = val);
14
+ return;
15
+ }
16
+ style[stylesOrProp] = value;
17
+ },
18
+ deleteProperty(getStyle, prop) {
19
+ getStyle()[prop] = null;
20
+ return true;
21
+ }
22
+ };
23
+ export const attribsProxy = {
24
+ get: (element, key) => {
25
+ return element.getAttribute(key);
26
+ },
27
+ set: (element, key, value) => {
28
+ element.setAttribute(key, value);
29
+ return true;
30
+ },
31
+ has: (element, key) => {
32
+ return element.hasAttribute(key);
33
+ },
34
+ ownKeys: (element) => {
35
+ return element.getAttributeNames();
36
+ },
37
+ };
38
+ export const eventsProxy = {
39
+ get: (element, key) => {
40
+ if (key == "addEventListener") {
41
+ return (name, handler) => element.addEventListener(name, handler);
42
+ }
43
+ if (key == "removeEventListener") {
44
+ return (name, handler) => element.removeEventListener(name, handler);
45
+ }
46
+ const listen = (handler) => {
47
+ const wrappedHandler = (event) => handler(event);
48
+ element.addEventListener(key, wrappedHandler);
49
+ return () => {
50
+ element.removeEventListener(key, wrappedHandler);
51
+ };
52
+ };
53
+ return new EventEmitter(listen);
54
+ }
55
+ };
@@ -0,0 +1,126 @@
1
+ import { EventEmitter } from "./emitter";
2
+ import { entityDataSymbol } from "./util";
3
+ export type ElementClassDescriptor = string | Record<string, boolean | undefined> | undefined | ElementClassDescriptor[];
4
+ export type DOMContent = number | null | string | Element | JelEntity<object> | Text | DOMContent[];
5
+ export type DomEntity<T extends HTMLElement> = JelEntity<ElementAPI<T>>;
6
+ type CSSValue = string | number | null | HexCodeContainer;
7
+ type HexCodeContainer = {
8
+ hexCode: string;
9
+ toString(): string;
10
+ };
11
+ export type StylesDescriptor = {
12
+ [K in keyof CSSStyleDeclaration as [
13
+ K,
14
+ CSSStyleDeclaration[K]
15
+ ] extends [string, string] ? K : never]+?: CSSValue;
16
+ };
17
+ export type StyleAccessor = StylesDescriptor & ((styles: StylesDescriptor) => void) & ((property: keyof StylesDescriptor, value: CSSValue) => void);
18
+ type ContentlessTag = "area" | "br" | "hr" | "iframe" | "input" | "textarea" | "img" | "canvas" | "link" | "meta" | "source" | "embed" | "track" | "base";
19
+ type TagWithHref = "a" | "link" | "base";
20
+ type TagWithSrc = "img" | "script" | "iframe" | "video" | "audio" | "embed" | "source" | "track";
21
+ type TagWithValue = "input" | "textarea";
22
+ type TagWithWidthHeight = "canvas" | "img" | "embed" | "iframe" | "video";
23
+ type TagWithType = "input" | "source" | "button";
24
+ type TagWithName = 'input' | 'textarea' | 'select' | 'form';
25
+ type ContentlessElement = HTMLElementTagNameMap[ContentlessTag];
26
+ export type ElementDescriptor<Tag extends string> = {
27
+ classes?: ElementClassDescriptor;
28
+ attribs?: Record<string, string | number | boolean>;
29
+ on?: {
30
+ [E in keyof HTMLElementEventMap]+?: (event: HTMLElementEventMap[E]) => void;
31
+ };
32
+ style?: StylesDescriptor;
33
+ cssVariables?: Record<string, CSSValue>;
34
+ } & (Tag extends TagWithValue ? {
35
+ value?: string | number;
36
+ } : {}) & (Tag extends ContentlessTag ? {} : {
37
+ content?: DOMContent;
38
+ }) & (Tag extends TagWithSrc ? {
39
+ src?: string;
40
+ } : {}) & (Tag extends TagWithHref ? {
41
+ href?: string;
42
+ } : {}) & (Tag extends TagWithWidthHeight ? {
43
+ width?: number;
44
+ height?: number;
45
+ } : {}) & (Tag extends TagWithType ? {
46
+ type?: string;
47
+ } : {}) & (Tag extends TagWithName ? {
48
+ name?: string;
49
+ } : {});
50
+ type ElementAPI<T extends HTMLElement> = {
51
+ readonly element: T;
52
+ readonly classes: DOMTokenList;
53
+ readonly attribs: {
54
+ [key: string]: string | null;
55
+ };
56
+ readonly events: EventsAccessor;
57
+ readonly style: StyleAccessor;
58
+ setCSSVariable(variableName: string, value: CSSValue): void;
59
+ setCSSVariable(table: Record<string, CSSValue>): void;
60
+ qsa(selector: string): (Element | DomEntity<HTMLElement>)[];
61
+ remove(): void;
62
+ getRect(): DOMRect;
63
+ focus(): void;
64
+ blur(): void;
65
+ on<E extends keyof HTMLElementEventMap>(eventId: E, handler: (this: ElementAPI<T>, data: HTMLElementEventMap[E]) => void): void;
66
+ } & (T extends ContentlessElement ? {} : {
67
+ append(...content: DOMContent[]): void;
68
+ innerHTML: string;
69
+ content: DOMContent;
70
+ }) & (T extends HTMLElementTagNameMap[TagWithValue] ? {
71
+ value: string;
72
+ select(): void;
73
+ } : {}) & (T extends HTMLCanvasElement ? {
74
+ width: number;
75
+ height: number;
76
+ getContext: HTMLCanvasElement["getContext"];
77
+ } : {}) & (T extends HTMLElementTagNameMap[TagWithSrc] ? {
78
+ src: string;
79
+ } : {}) & (T extends HTMLElementTagNameMap[TagWithHref] ? {
80
+ href: string;
81
+ } : {}) & (T extends HTMLMediaElement ? {
82
+ play(): void;
83
+ pause(): void;
84
+ currentTime: number;
85
+ readonly paused: boolean;
86
+ } : {}) & (T extends HTMLElementTagNameMap[TagWithName] ? {
87
+ name: string | null;
88
+ } : {});
89
+ export type DomHelper = ((
90
+ /**
91
+ * Creates an element of the specified tag
92
+ */
93
+ <T extends keyof HTMLElementTagNameMap>(tagName: T, descriptor: ElementDescriptor<T>) => DomEntity<HTMLElementTagNameMap[T]>) & (
94
+ /**
95
+ * Creates an element of the specified tag
96
+ */
97
+ <T extends keyof HTMLElementTagNameMap>(tagName: T) => DomEntity<HTMLElementTagNameMap[T]>) & (
98
+ /**
99
+ * Creates an element with ID and classes as specified by a selector-like string
100
+ */
101
+ <T extends keyof HTMLElementTagNameMap>(selector: `${T}#${string}`, content?: T extends ContentlessTag ? void : DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & (
102
+ /**
103
+ * Creates an element with ID and classes as specified by a selector-like string
104
+ */
105
+ <T extends keyof HTMLElementTagNameMap>(selector: `${T}.${string}`, content?: T extends ContentlessTag ? void : DOMContent) => DomEntity<HTMLElementTagNameMap[T]>) & (
106
+ /**
107
+ * Wraps an existing element as a DomEntity
108
+ */
109
+ <T extends HTMLElement>(element: T) => DomEntity<T>) & {
110
+ [T in keyof HTMLElementTagNameMap]: (descriptor: ElementDescriptor<T>) => DomEntity<HTMLElementTagNameMap[T]>;
111
+ } & {
112
+ [T in keyof HTMLElementTagNameMap]: T extends ContentlessTag ? () => DomEntity<HTMLElementTagNameMap[T]> : (content?: DOMContent) => DomEntity<HTMLElementTagNameMap[T]>;
113
+ });
114
+ type JelEntityData = {
115
+ dom: DOMContent;
116
+ };
117
+ export type JelEntity<API extends object | void> = (API extends void ? {} : API) & {
118
+ readonly [entityDataSymbol]: JelEntityData;
119
+ };
120
+ export type EventsAccessor = {
121
+ [K in keyof HTMLElementEventMap]: EventEmitter<HTMLElementEventMap[K]>;
122
+ } & {
123
+ addEventListener<K extends keyof HTMLElementEventMap>(eventName: K, listener: (event: HTMLElementEventMap[K]) => void): void;
124
+ removeEventListener<K extends keyof HTMLElementEventMap>(eventName: K, listener: (event: HTMLElementEventMap[K]) => void): void;
125
+ };
126
+ export {};
@@ -0,0 +1 @@
1
+ import { entityDataSymbol } from "./util";
@@ -0,0 +1,11 @@
1
+ import { DOMContent, ElementDescriptor, JelEntity } from "./types";
2
+ export declare const entityDataSymbol: unique symbol;
3
+ export declare const isContent: (value: DOMContent | ElementDescriptor<string> | undefined) => value is DOMContent;
4
+ export declare function isJelEntity(content: DOMContent): content is JelEntity<object>;
5
+ /**
6
+ * Wraps an object such that it can be appended as DOM content while retaining its original API
7
+ * @param content
8
+ * @param api
9
+ */
10
+ export declare function createEntity<API extends object>(content: DOMContent, api: API extends DOMContent ? never : API): JelEntity<API>;
11
+ export declare function createEntity(content: DOMContent): JelEntity<void>;
@@ -0,0 +1,28 @@
1
+ export const entityDataSymbol = Symbol("jelComponentData");
2
+ export const isContent = (value) => {
3
+ if (value === undefined)
4
+ return false;
5
+ return typeof value == "string"
6
+ || typeof value == "number"
7
+ || !value
8
+ || value instanceof Element
9
+ || value instanceof Text
10
+ || entityDataSymbol in value
11
+ || (Array.isArray(value) && value.every(isContent));
12
+ };
13
+ export function isJelEntity(content) {
14
+ return typeof content == "object" && !!content && entityDataSymbol in content;
15
+ }
16
+ export function createEntity(content, api) {
17
+ if (isContent(api)) {
18
+ throw new TypeError("API object is already valid content");
19
+ }
20
+ return Object.create(api !== null && api !== void 0 ? api : {}, {
21
+ [entityDataSymbol]: {
22
+ value: {
23
+ dom: content
24
+ }
25
+ },
26
+ });
27
+ }
28
+ ;
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@xtia/jel",
3
- "version": "0.4.5",
3
+ "version": "0.5.0",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/jel-ts",
6
6
  "type": "github"
7
7
  },
8
+ "sideEffects": false,
9
+ "types": "./index.d.ts",
10
+ "main": "./index.js",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./index.d.ts",
14
+ "default": "./index.js"
15
+ },
16
+ "./internal/*": null
17
+ },
8
18
  "description": "Lightweight DOM manipulation and componentisation",
9
- "main": "index.js",
10
19
  "keywords": [
11
20
  "dom"
12
21
  ],