@xtia/jel 0.5.0 → 0.6.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.
@@ -97,6 +97,30 @@ export const $ = new Proxy(createElement, {
97
97
  };
98
98
  }
99
99
  });
100
+ const elementMutationMap = new WeakMap();
101
+ let mutationObserver = null;
102
+ function observeMutations() {
103
+ if (mutationObserver !== null)
104
+ return;
105
+ mutationObserver = new MutationObserver((mutations) => {
106
+ mutations.forEach(mut => {
107
+ mut.addedNodes.forEach(node => {
108
+ if (elementMutationMap.has(node)) {
109
+ elementMutationMap.get(node).add();
110
+ }
111
+ });
112
+ mut.removedNodes.forEach(node => {
113
+ if (elementMutationMap.has(node)) {
114
+ elementMutationMap.get(node).remove();
115
+ }
116
+ });
117
+ });
118
+ });
119
+ mutationObserver.observe(document.body, {
120
+ childList: true,
121
+ subtree: true
122
+ });
123
+ }
100
124
  function getWrappedElement(element) {
101
125
  if (!elementWrapCache.has(element)) {
102
126
  const setCSSVariable = (k, v) => {
@@ -107,6 +131,57 @@ function getWrappedElement(element) {
107
131
  element.style.setProperty("--" + k, v);
108
132
  }
109
133
  };
134
+ const styleListeners = {};
135
+ function addStyleListener(prop, source) {
136
+ const subscribe = "subscribe" in source
137
+ ? () => source.subscribe(v => element.style[prop] = v)
138
+ : () => source.listen(v => element.style[prop] = v);
139
+ styleListeners[prop] = {
140
+ subscribe,
141
+ unsubscribe: element.isConnected ? subscribe() : null,
142
+ };
143
+ if (!elementMutationMap.has(element)) {
144
+ elementMutationMap.set(element, {
145
+ add: () => {
146
+ Object.values(styleListeners).forEach(l => { var _a; return l.unsubscribe = (_a = l.subscribe) === null || _a === void 0 ? void 0 : _a.call(l); });
147
+ },
148
+ remove: () => {
149
+ Object.values(styleListeners).forEach(l => {
150
+ var _a;
151
+ (_a = l.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(l);
152
+ l.unsubscribe = null;
153
+ });
154
+ }
155
+ });
156
+ }
157
+ observeMutations();
158
+ }
159
+ function removeStyleListener(prop) {
160
+ if (styleListeners[prop].unsubscribe) {
161
+ styleListeners[prop].unsubscribe();
162
+ }
163
+ delete styleListeners[prop];
164
+ if (Object.keys(styleListeners).length == 0) {
165
+ elementMutationMap.delete(element);
166
+ }
167
+ }
168
+ function setStyle(prop, value) {
169
+ if (styleListeners[prop])
170
+ removeStyleListener(prop);
171
+ if (typeof value == "object" && value) {
172
+ if ("listen" in value || "subscribe" in value) {
173
+ addStyleListener(prop, value);
174
+ return;
175
+ }
176
+ value = value.toString();
177
+ }
178
+ if (value === undefined) {
179
+ return prop in styleListeners
180
+ ? styleListeners[prop].subscribe
181
+ : element.style[prop];
182
+ }
183
+ element.style[prop] = value;
184
+ }
110
185
  const domEntity = {
111
186
  [entityDataSymbol]: {
112
187
  dom: element,
@@ -210,7 +285,7 @@ function getWrappedElement(element) {
210
285
  element.setAttribute("name", v);
211
286
  }
212
287
  },
213
- style: new Proxy(() => element.style, styleProxy),
288
+ style: new Proxy(setStyle, styleProxy),
214
289
  classes: element.classList,
215
290
  events: new Proxy(element, eventsProxy)
216
291
  };
@@ -74,11 +74,32 @@ export declare class EventEmitter<T> extends Emitter<T> {
74
74
  throttle(ms: number): EventEmitter<T>;
75
75
  batch(ms: number): Emitter<T[]>;
76
76
  filter(check: (value: T) => boolean): EventEmitter<T>;
77
+ /**
78
+ * **Experimental**: May change in future revisions
79
+ * Note: potential leak - This link will remain subscribed to the parent
80
+ * until it emits, regardless of subscriptions to this link.
81
+ * @param notifier
82
+ * @returns
83
+ */
77
84
  once(): EventEmitter<T>;
78
85
  scan<S>(updater: (state: S, value: T) => S, initial: S): EventEmitter<S>;
79
86
  buffer(count: number): EventEmitter<T[]>;
87
+ /**
88
+ * **Experimental**: May change in future revisions
89
+ * Note: potential leak - This link will remain subscribed to the parent
90
+ * until emission limit is reached, regardless of subscriptions to this link.
91
+ * @param notifier
92
+ * @returns
93
+ */
80
94
  take(limit: number): EventEmitter<T>;
81
95
  tap(cb: Handler<T>): EventEmitter<T>;
96
+ /**
97
+ * **Experimental**: May change in future revisions
98
+ * Note: potential leak - This link will remain subscribed to the notifier
99
+ * until it emits, regardless of subscriptions to this link.
100
+ * @param notifier
101
+ * @returns
102
+ */
82
103
  takeUntil(notifier: Emitter<any>): Emitter<T>;
83
104
  }
84
105
  /**
@@ -126,10 +126,10 @@ export class EventEmitter extends Emitter {
126
126
  return new EventEmitter(listen);
127
127
  }
128
128
  throttle(ms) {
129
- let lastTime = 0;
129
+ let lastTime = -Infinity;
130
130
  const listen = this.transform((value, emit) => {
131
- const now = Date.now();
132
- if (now < lastTime + ms) {
131
+ const now = performance.now();
132
+ if (now >= lastTime + ms) {
133
133
  lastTime = now;
134
134
  emit(value);
135
135
  }
@@ -156,6 +156,13 @@ export class EventEmitter extends Emitter {
156
156
  const listen = this.transform((value, emit) => check(value) && emit(value));
157
157
  return new EventEmitter(listen);
158
158
  }
159
+ /**
160
+ * **Experimental**: May change in future revisions
161
+ * Note: potential leak - This link will remain subscribed to the parent
162
+ * until it emits, regardless of subscriptions to this link.
163
+ * @param notifier
164
+ * @returns
165
+ */
159
166
  once() {
160
167
  const { emit, listen } = createListenable();
161
168
  const unsub = this.apply(v => {
@@ -183,6 +190,13 @@ export class EventEmitter extends Emitter {
183
190
  });
184
191
  return new EventEmitter(listen);
185
192
  }
193
+ /**
194
+ * **Experimental**: May change in future revisions
195
+ * Note: potential leak - This link will remain subscribed to the parent
196
+ * until emission limit is reached, regardless of subscriptions to this link.
197
+ * @param notifier
198
+ * @returns
199
+ */
186
200
  take(limit) {
187
201
  const { emit, listen } = createListenable();
188
202
  let count = 0;
@@ -204,6 +218,13 @@ export class EventEmitter extends Emitter {
204
218
  });
205
219
  return new EventEmitter(listen);
206
220
  }
221
+ /**
222
+ * **Experimental**: May change in future revisions
223
+ * Note: potential leak - This link will remain subscribed to the notifier
224
+ * until it emits, regardless of subscriptions to this link.
225
+ * @param notifier
226
+ * @returns
227
+ */
207
228
  takeUntil(notifier) {
208
229
  const { emit, listen } = createListenable();
209
230
  const unsub = this.apply(v => {
@@ -213,7 +234,7 @@ export class EventEmitter extends Emitter {
213
234
  unsub();
214
235
  unsubNotifier();
215
236
  });
216
- return new Emitter(listen);
237
+ return new EventEmitter(listen);
217
238
  }
218
239
  }
219
240
  /**
@@ -1,3 +1,4 @@
1
- export declare const styleProxy: ProxyHandler<() => CSSStyleDeclaration>;
1
+ import { SetGetStyleFunc } from "./types";
2
+ export declare const styleProxy: ProxyHandler<SetGetStyleFunc>;
2
3
  export declare const attribsProxy: ProxyHandler<HTMLElement>;
3
4
  export declare const eventsProxy: ProxyHandler<HTMLElement>;
package/internal/proxy.js CHANGED
@@ -1,22 +1,21 @@
1
1
  import { EventEmitter } from "./emitter";
2
2
  export const styleProxy = {
3
- get(getStyle, prop) {
4
- return getStyle()[prop];
3
+ get(style, prop) {
4
+ return style(prop);
5
5
  },
6
- set(getStyle, prop, value) {
7
- getStyle()[prop] = value;
6
+ set(style, prop, value) {
7
+ style(prop, value);
8
8
  return true;
9
9
  },
10
- apply(getStyle, _, [stylesOrProp, value]) {
11
- const style = getStyle();
10
+ apply(style, _, [stylesOrProp, value]) {
12
11
  if (typeof stylesOrProp == "object") {
13
- Object.entries(stylesOrProp).forEach(([prop, val]) => style[prop] = val);
12
+ Object.entries(stylesOrProp).forEach(([prop, val]) => style(prop, val));
14
13
  return;
15
14
  }
16
- style[stylesOrProp] = value;
15
+ style(stylesOrProp, value);
17
16
  },
18
- deleteProperty(getStyle, prop) {
19
- getStyle()[prop] = null;
17
+ deleteProperty(style, prop) {
18
+ style(prop, null);
20
19
  return true;
21
20
  }
22
21
  };
@@ -1,9 +1,15 @@
1
- import { EventEmitter } from "./emitter";
1
+ import { EventEmitter, UnsubscribeFunc } from "./emitter";
2
2
  import { entityDataSymbol } from "./util";
3
3
  export type ElementClassDescriptor = string | Record<string, boolean | undefined> | undefined | ElementClassDescriptor[];
4
4
  export type DOMContent = number | null | string | Element | JelEntity<object> | Text | DOMContent[];
5
5
  export type DomEntity<T extends HTMLElement> = JelEntity<ElementAPI<T>>;
6
- type CSSValue = string | number | null | HexCodeContainer;
6
+ export type ReactiveSource<T> = ({
7
+ listen: (handler: (value: T) => void) => UnsubscribeFunc;
8
+ } | {
9
+ subscribe: (handler: (value: T) => void) => UnsubscribeFunc;
10
+ });
11
+ export type CSSValue = string | number | null | HexCodeContainer;
12
+ export type CSSProperty = keyof StylesDescriptor;
7
13
  type HexCodeContainer = {
8
14
  hexCode: string;
9
15
  toString(): string;
@@ -12,9 +18,11 @@ export type StylesDescriptor = {
12
18
  [K in keyof CSSStyleDeclaration as [
13
19
  K,
14
20
  CSSStyleDeclaration[K]
15
- ] extends [string, string] ? K : never]+?: CSSValue;
21
+ ] extends [string, string] ? K : never]+?: CSSValue | ReactiveSource<CSSValue>;
16
22
  };
17
- export type StyleAccessor = StylesDescriptor & ((styles: StylesDescriptor) => void) & ((property: keyof StylesDescriptor, value: CSSValue) => void);
23
+ export type SetStyleFunc = ((property: CSSProperty, value: CSSValue | ReactiveSource<CSSValue>) => void);
24
+ export type SetGetStyleFunc = SetStyleFunc & ((property: CSSProperty) => string | ReactiveSource<CSSValue>);
25
+ export type StyleAccessor = ((styles: StylesDescriptor) => void) & StylesDescriptor & SetStyleFunc;
18
26
  type ContentlessTag = "area" | "br" | "hr" | "iframe" | "input" | "textarea" | "img" | "canvas" | "link" | "meta" | "source" | "embed" | "track" | "base";
19
27
  type TagWithHref = "a" | "link" | "base";
20
28
  type TagWithSrc = "img" | "script" | "iframe" | "video" | "audio" | "embed" | "source" | "track";
@@ -34,7 +42,7 @@ export type ElementDescriptor<Tag extends string> = {
34
42
  } & (Tag extends TagWithValue ? {
35
43
  value?: string | number;
36
44
  } : {}) & (Tag extends ContentlessTag ? {} : {
37
- content?: DOMContent;
45
+ content?: DOMContent | ReactiveSource<DOMContent>;
38
46
  }) & (Tag extends TagWithSrc ? {
39
47
  src?: string;
40
48
  } : {}) & (Tag extends TagWithHref ? {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtia/jel",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/jel-ts",
6
6
  "type": "github"