p-elements-core 1.2.17 → 1.2.18

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.
@@ -38,84 +38,196 @@ declare interface ProjectionOptions extends ProjectorOptions {
38
38
  ) => Function;
39
39
  }
40
40
 
41
- declare interface VNodeProperties {
42
- enterAnimation?:
43
- | ((element: Element, properties?: VNodeProperties) => void)
44
- | string;
45
- exitAnimation?:
46
- | ((
47
- element: Element,
48
- removeElement: () => void,
49
- properties?: VNodeProperties
50
- ) => void)
51
- | string;
52
- updateAnimation?: (
41
+ export type EventHandler = (this: Node, event: Event) => boolean | undefined | void;
42
+
43
+
44
+ /**
45
+ * Object containing attributes, properties, event handlers and more that can be put on DOM nodes.
46
+ *
47
+ * For your convenience, all common attributes, properties and event handlers are listed here and are
48
+ * type-checked when using Typescript.
49
+ */
50
+ export interface VNodeProperties {
51
+ /**
52
+ * The animation to perform when this node is added to an already existing parent.
53
+ * {@link http://maquettejs.org/docs/animations.html More about animations}.
54
+ * @param element - Element that was just added to the DOM.
55
+ * @param properties - The properties object that was supplied to the [[h]] method
56
+ */
57
+ enterAnimation?: (element: Element, properties?: VNodeProperties) => void;
58
+ /**
59
+ * The animation to perform when this node is removed while its parent remains.
60
+ * @param element - Element that ought to be removed from to the DOM.
61
+ * @param removeElement - Function that removes the element from the DOM.
62
+ * This argument is provided purely for convenience.
63
+ * You may use this function to remove the element when the animation is done.
64
+ * @param properties - The properties object that was supplied to the [[h]] method that rendered this [[VNode]] the previous time.
65
+ */
66
+ exitAnimation?(element: Element, removeElement: () => void, properties?: VNodeProperties): void;
67
+ /**
68
+ * The animation to perform when the properties of this node change.
69
+ * This also includes attributes, styles, css classes. This callback is also invoked when node contains only text and that text changes.
70
+ * {@link http://maquettejs.org/docs/animations.html More about animations}.
71
+ * @param element - Element that was modified in the DOM.
72
+ * @param properties - The last properties object that was supplied to the [[h]] method
73
+ * @param previousProperties - The previous properties object that was supplied to the [[h]] method
74
+ */
75
+ updateAnimation?(
53
76
  element: Element,
54
77
  properties?: VNodeProperties,
55
78
  previousProperties?: VNodeProperties
56
- ) => void;
79
+ ): void;
80
+ /**
81
+ * Callback that is executed after this node is added to the DOM. Child nodes and properties have
82
+ * already been applied.
83
+ * @param element - The element that was added to the DOM.
84
+ * @param projectionOptions - The projection options that were used, see [[createProjector]].
85
+ * @param vnodeSelector - The selector passed to the [[h]] function.
86
+ * @param properties - The properties passed to the [[h]] function.
87
+ * @param children - The children that were created.
88
+ */
57
89
  afterCreate?(
58
90
  element: Element,
59
91
  projectionOptions: ProjectionOptions,
60
92
  vnodeSelector: string,
61
93
  properties: VNodeProperties,
62
- children: VNode[]
94
+ children: VNode[] | undefined
63
95
  ): void;
96
+ /**
97
+ * Callback that is executed every time this node may have been updated. Child nodes and properties
98
+ * have already been updated.
99
+ * @param element - The element that may have been updated in the DOM.
100
+ * @param projectionOptions - The projection options that were used, see [[createProjector]].
101
+ * @param vnodeSelector - The selector passed to the [[h]] function.
102
+ * @param properties - The properties passed to the [[h]] function.
103
+ * @param children - The children for this node.
104
+ */
64
105
  afterUpdate?(
65
106
  element: Element,
66
107
  projectionOptions: ProjectionOptions,
67
108
  vnodeSelector: string,
68
109
  properties: VNodeProperties,
69
- children: VNode[]
110
+ children: VNode[] | undefined
70
111
  ): void;
112
+
113
+ /**
114
+ * Callback that is called when a node has been removed from the tree.
115
+ * The callback is called during idle state or after a timeout (fallback).
116
+ * {@link https://maquettejs.org/docs/dom-node-removal.html More info}
117
+ * @param element - The element that has been removed from the DOM.
118
+ */
71
119
  afterRemoved?(element: Element): void;
72
- readonly bind?: Object;
73
- readonly key?: Object;
120
+
121
+ /**
122
+ * When specified, the event handlers will be invoked with 'this' pointing to the value.
123
+ * This is useful when using the prototype/class based implementation of MaquetteComponents.
124
+ *
125
+ * When no [[key]] is present, this object is also used to uniquely identify a DOM node.
126
+ */
127
+ readonly bind?: unknown;
128
+
129
+ /**
130
+ * Used to uniquely identify a DOM node among siblings.
131
+ * A key is required when there are more children with the same selector and these children are added or removed dynamically.
132
+ * NOTE: this does not have to be a string or number, a [[MaquetteComponent]] Object for instance is also common.
133
+ */
134
+ readonly key?: unknown;
135
+
136
+ /**
137
+ * An object containing event handlers to attach using addEventListener.
138
+ * Note that `projector.scheduleRender()` is called automatically when these event handlers are invoked.
139
+ */
140
+ readonly on?: {
141
+ [eventName: string]:
142
+ | EventHandler
143
+ | {
144
+ listener: EventHandler;
145
+ options: { capture?: boolean; passive?: boolean; once?: boolean };
146
+ };
147
+ };
148
+
149
+ /**
150
+ * An object containing event handlers to attach using addEventListener.
151
+ * Note that `projector.scheduleRender()` is called automatically when these event handlers are invoked.
152
+ */
153
+ readonly onCapture?: { [eventName: string]: EventHandler };
154
+
155
+ /**
156
+ * An object literal like `{important:true}` which allows css classes, like `important` to be added and removed
157
+ * dynamically.
158
+ */
74
159
  readonly classes?: { [index: string]: boolean | null | undefined };
75
- readonly styles?: { [index: string]: string | null | undefined };
76
160
 
77
- ontouchcancel?(ev?: TouchEvent): boolean | void;
78
- ontouchend?(ev?: TouchEvent): boolean | void;
79
- ontouchmove?(ev?: TouchEvent): boolean | void;
80
- ontouchstart?(ev?: TouchEvent): boolean | void;
161
+ /**
162
+ * An object literal like `{height:'100px'}` which allows styles to be changed dynamically. All values must be strings.
163
+ */
164
+ readonly styles?: Partial<CSSStyleDeclaration> | { [cssVariable: string]: string };
165
+
166
+ /**
167
+ * For custom elements
168
+ */
169
+ readonly is?: string;
81
170
 
171
+ // From Element
172
+ ontouchcancel?(ev: TouchEvent): boolean | void;
173
+ ontouchend?(ev: TouchEvent): boolean | void;
174
+ ontouchmove?(ev: TouchEvent): boolean | void;
175
+ ontouchstart?(ev: TouchEvent): boolean | void;
176
+ // From HTMLFormElement
82
177
  readonly action?: string;
83
178
  readonly encoding?: string;
84
179
  readonly enctype?: string;
85
180
  readonly method?: string;
86
181
  readonly name?: string;
87
182
  readonly target?: string;
88
-
183
+ // From HTMLAnchorElement
89
184
  readonly href?: string;
90
185
  readonly rel?: string;
91
-
92
- onblur?(ev?: FocusEvent): boolean | void;
93
- onchange?(ev?: Event): boolean | void;
94
- onclick?(ev?: MouseEvent): boolean | void;
95
- ondblclick?(ev?: MouseEvent): boolean | void;
96
- onfocus?(ev?: FocusEvent): boolean | void;
97
- oninput?(ev?: Event): boolean | void;
98
- onkeydown?(ev?: KeyboardEvent): boolean | void;
99
- onkeypress?(ev?: KeyboardEvent): boolean | void;
100
- onkeyup?(ev?: KeyboardEvent): boolean | void;
101
- onload?(ev?: Event): boolean | void;
102
- onmousedown?(ev?: MouseEvent): boolean | void;
103
- onmouseenter?(ev?: MouseEvent): boolean | void;
104
- onmouseleave?(ev?: MouseEvent): boolean | void;
105
- onmousemove?(ev?: MouseEvent): boolean | void;
106
- onmouseout?(ev?: MouseEvent): boolean | void;
107
- onmouseover?(ev?: MouseEvent): boolean | void;
108
- onmouseup?(ev?: MouseEvent): boolean | void;
109
- onmousewheel?(ev?: WheelEvent): boolean | void;
110
- onscroll?(ev?: UIEvent): boolean | void;
111
- onsubmit?(ev?: Event): boolean | void;
186
+ // From HTMLElement
187
+ onblur?(ev: FocusEvent): boolean | void;
188
+ onchange?(ev: Event): boolean | void;
189
+ onclick?(ev: MouseEvent): boolean | void;
190
+ ondblclick?(ev: MouseEvent): boolean | void;
191
+ ondrag?(ev: DragEvent): boolean | void;
192
+ ondragend?(ev: DragEvent): boolean | void;
193
+ ondragenter?(ev: DragEvent): boolean | void;
194
+ ondragleave?(ev: DragEvent): boolean | void;
195
+ ondragover?(ev: DragEvent): boolean | void;
196
+ ondragstart?(ev: DragEvent): boolean | void;
197
+ ondrop?(ev: DragEvent): boolean | void;
198
+ onfocus?(ev: FocusEvent): boolean | void;
199
+ oninput?(ev: Event): boolean | void;
200
+ onkeydown?(ev: KeyboardEvent): boolean | void;
201
+ onkeypress?(ev: KeyboardEvent): boolean | void;
202
+ onkeyup?(ev: KeyboardEvent): boolean | void;
203
+ onload?(ev: Event): boolean | void;
204
+ onmousedown?(ev: MouseEvent): boolean | void;
205
+ onmouseenter?(ev: MouseEvent): boolean | void;
206
+ onmouseleave?(ev: MouseEvent): boolean | void;
207
+ onmousemove?(ev: MouseEvent): boolean | void;
208
+ onmouseout?(ev: MouseEvent): boolean | void;
209
+ onmouseover?(ev: MouseEvent): boolean | void;
210
+ onmouseup?(ev: MouseEvent): boolean | void;
211
+ onmousewheel?(ev: WheelEvent): boolean | void;
212
+ onscroll?(ev: UIEvent): boolean | void;
213
+ onsubmit?(ev: Event): boolean | void;
214
+ onpointercancel?(ev: PointerEvent): boolean | void;
215
+ onpointerdown?(ev: PointerEvent): boolean | void;
216
+ onpointerenter?(ev: PointerEvent): boolean | void;
217
+ onpointerleave?(ev: PointerEvent): boolean | void;
218
+ onpointermove?(ev: PointerEvent): boolean | void;
219
+ onpointerout?(ev: PointerEvent): boolean | void;
220
+ onpointerover?(ev: PointerEvent): boolean | void;
221
+ onpointerup?(ev: PointerEvent): boolean | void;
112
222
  readonly spellcheck?: boolean;
113
223
  readonly tabIndex?: number;
114
224
  readonly disabled?: boolean;
115
225
  readonly title?: string;
116
226
  readonly accessKey?: string;
227
+ readonly class?: string;
117
228
  readonly id?: string;
118
-
229
+ readonly draggable?: boolean;
230
+ // From HTMLInputElement
119
231
  readonly type?: string;
120
232
  readonly autocomplete?: string;
121
233
  readonly checked?: boolean;
@@ -123,11 +235,25 @@ declare interface VNodeProperties {
123
235
  readonly readOnly?: boolean;
124
236
  readonly src?: string;
125
237
  readonly value?: string;
126
-
238
+ // From HTMLImageElement
127
239
  readonly alt?: string;
128
240
  readonly srcset?: string;
241
+ /**
242
+ * Puts a non-interactive string of html inside the DOM node.
243
+ *
244
+ * Note: if you use innerHTML, maquette cannot protect you from XSS vulnerabilities and you must make sure that the innerHTML value is safe.
245
+ */
129
246
  readonly innerHTML?: string;
130
247
 
248
+ /**
249
+ * Do not use className, use class instead
250
+ */
251
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
252
+ readonly className?: never | "Hint: do not use `className`, use `class` instead";
253
+
254
+ /**
255
+ * Everything that is not explicitly listed (properties and attributes that are either uncommon or custom).
256
+ */
131
257
  readonly [index: string]: any;
132
258
  }
133
259
 
@@ -182,9 +308,21 @@ declare const CustomElementConfig: (
182
308
 
183
309
  declare type ElementProjectorMode = "append" | "merge" | "replace";
184
310
 
311
+ export interface PropertyOptions {
312
+ type?: PropertyType;
313
+ reflect?: boolean;
314
+ attribute?: string;
315
+ readonly?: boolean;
316
+ converter?: AttributeConverter<any>;
317
+ }
318
+ export interface PropertyInfo extends PropertyOptions {
319
+ name: string;
320
+ }
321
+
185
322
  declare abstract class CustomElement extends HTMLElement {
186
323
  constructor(self?: any);
187
324
  get internals(): ElementInternals;
325
+ get properties(): readonly PropertyInfo[];
188
326
  protected addStylesheetToRootNode(
189
327
  style: string,
190
328
  rootNode: ShadowRoot | Document
@@ -199,6 +337,7 @@ declare abstract class CustomElement extends HTMLElement {
199
337
  render: () => VNode
200
338
  ): Promise<Projector>;
201
339
  addController(controller: CustomElementController): void;
340
+ scheduleRender(): void;
202
341
  renderNow(): void;
203
342
  connectedCallback(): void;
204
343
  disconnectedCallback(): void;
@@ -215,6 +354,7 @@ declare abstract class CustomElementController {
215
354
  constructor(hostElement: CustomElement);
216
355
  hostElement: CustomElement;
217
356
  renderNow(): void;
357
+ scheduleRender(): void;
218
358
  init?(): void;
219
359
  connected?(): void;
220
360
  disconnected?(): void;
@@ -241,6 +381,7 @@ interface AttributeConverter<T> {
241
381
  fromAttribute?(value: string | null): T;
242
382
  toAttribute?(value: T): string;
243
383
  }
384
+
244
385
  declare type PropertyType = "string" | "number" | "boolean" | "object";
245
386
 
246
387
  declare const Property: (options?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "p-elements-core",
3
- "version": "1.2.17",
3
+ "version": "1.2.18",
4
4
  "description": "P Elements Core V1",
5
5
  "main": "dist/p-elements-core.js",
6
6
  "types": "p-elements-core.d.ts",
package/readme.md CHANGED
@@ -192,6 +192,14 @@ Before a property is set to a new value
192
192
 
193
193
  After a property value is set to a new value
194
194
 
195
+ #### `renderStart`
196
+
197
+ When the custom element start the rendering
198
+
199
+ #### `renderDone`
200
+
201
+ When the custom element is finished rendering
202
+
195
203
  #### `attributeChangedCallback`
196
204
 
197
205
  Call `super.attributeChangedCallback()` first.
@@ -1,8 +1,12 @@
1
+ import type { CustomElement } from './custom-element';
2
+
1
3
  export interface ICustomElementController {
2
4
  renderNow(): void;
3
5
  init?: () => void;
4
6
  connected? : () => void;
5
7
  disconnected? : () => void;
8
+ hostRenderStart?: () => void;
9
+ hostRenderDone? : () => void;
6
10
  hostElement: CustomElement;
7
11
  }
8
12
  export abstract class CustomElementController implements ICustomElementController {
@@ -20,4 +24,8 @@ export abstract class CustomElementController implements ICustomElementControlle
20
24
  renderNow() {
21
25
  this.hostElement?.renderNow();
22
26
  }
27
+
28
+ scheduleRender() {
29
+ this.hostElement?.scheduleRender();
30
+ }
23
31
  }
@@ -1,6 +1,7 @@
1
1
  import { PropertyInfo } from "./decorators/property";
2
2
  import { replaceApplyToCssVars } from "./helpers/css";
3
3
  import { ICustomElementController } from "./custom-element-controller";
4
+ import { Projector, VNode } from "./maquette/interfaces";
4
5
 
5
6
  export type ElementProjectorMode = "append" | "merge" | "replace";
6
7
 
@@ -102,6 +103,7 @@ export abstract class CustomElement extends HTMLElement {
102
103
  this.#polyfillCssApply();
103
104
  });
104
105
  }
106
+
105
107
  }
106
108
 
107
109
  #reflectProperties() {
@@ -272,18 +274,28 @@ export abstract class CustomElement extends HTMLElement {
272
274
  let projector: Projector;
273
275
  const mode = this.#projectorMode ? this.#projectorMode : "append";
274
276
  requestAnimationFrame(() => {
275
- projector = (window as any).Maquette.createProjector();
277
+ projector = (window as any).Maquette.createProjector({
278
+ performanceLogger: (eventName) => {
279
+ if (eventName === "renderStart" || eventName === "renderDone") {
280
+ this.#invokeRenderLifecycleFn(eventName);
281
+ }
282
+ }
283
+ });
276
284
  projector[mode](element, render.bind(this));
277
285
  this.#projector = projector;
278
286
  this.#canReflect = true;
279
287
  this.#reflectProperties();
280
- resolve(projector);
281
-
288
+ projector.renderNow();
289
+ resolve(projector);
282
290
  this.dispatchEvent(new CustomEvent("firstRender", {}));
283
291
  });
284
292
  });
285
293
  }
286
294
 
295
+ scheduleRender(): void {
296
+ this.#projector?.scheduleRender();
297
+ }
298
+
287
299
  renderNow(): void {
288
300
  this.#projector?.renderNow();
289
301
  }
@@ -328,4 +340,26 @@ export abstract class CustomElement extends HTMLElement {
328
340
  this[prop.name] = JSON.parse(newValue);
329
341
  }
330
342
  }
343
+
344
+ #isFirstRenderStart = true;
345
+
346
+ #isFirstRenderDone = true;
347
+
348
+
349
+ #invokeRenderLifecycleFn(eventName: string) {
350
+ if (this[eventName]){
351
+ this[eventName](eventName === "renderStart" ? this.#isFirstRenderStart : this.#isFirstRenderDone);
352
+ }
353
+ const controllerEventName = `host${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`;
354
+ this.#controllers.forEach((controller) => {
355
+ if (controller[controllerEventName]) {
356
+ controller[controllerEventName](eventName === "renderStart" ? this.#isFirstRenderStart : this.#isFirstRenderDone);
357
+ }
358
+ });
359
+ if (eventName === "renderStart") {
360
+ this.#isFirstRenderStart = false;
361
+ } else {
362
+ this.#isFirstRenderDone = false;
363
+ }
364
+ }
331
365
  }
@@ -61,7 +61,7 @@ export const property = (propertyOptions: PropertyOptions) => {
61
61
  },
62
62
  set: function (value) {
63
63
  this[sym] = value;
64
- this?.renderNow();
64
+ this?.scheduleRender();
65
65
  delete this[name];
66
66
  Object.defineProperty(this, name, {
67
67
  configurable: true,
@@ -102,7 +102,7 @@ export const property = (propertyOptions: PropertyOptions) => {
102
102
  }
103
103
  const handleChange = () => {
104
104
  if (this[sym] !== oldValue) {
105
- this?.renderNow && this.renderNow();
105
+ this?.scheduleRender && this.scheduleRender();
106
106
  this?.updated && this.updated(name, oldValue, this[sym]);
107
107
  }
108
108
  };
@@ -135,7 +135,7 @@ export const property = (propertyOptions: PropertyOptions) => {
135
135
  }
136
136
  }
137
137
  handleChange();
138
- this?.renderNow && this.renderNow();
138
+ this?.scheduleRender && this.scheduleRender();
139
139
  },
140
140
  });
141
141
  };
@@ -1,6 +1,7 @@
1
1
  export interface HighlightableInterface {
2
2
  highlight: boolean;
3
3
  }
4
+ import type { CustomElement } from "../../custom-element";
4
5
 
5
6
  type Constructor<T> = new (...args: any[]) => T;
6
7
 
@@ -1,6 +1,13 @@
1
1
  import { Highlightable } from "./mixin/highlight";
2
2
  import anime from "animejs";
3
3
 
4
+ import {customElementConfig as CustomElementConfig} from "../decorators/custom-element-config";
5
+ import {property as Property, AttributeConverter} from "../decorators/property";
6
+ import {propertyRenderOnSet as PropertyRenderOnSet} from "../decorators/render-property-on-set";
7
+ import {query as Query} from "../decorators/query";
8
+ import {CustomElement} from "../custom-element";
9
+ import {CustomElementController} from "../custom-element-controller";
10
+
4
11
  @CustomElementConfig({
5
12
  tagName: "my-button",
6
13
  })
@@ -10,7 +17,6 @@ export class MyButton extends CustomElement {
10
17
  render = () => {
11
18
  return <button class="foo"><slot></slot></button>;
12
19
  }
13
-
14
20
  }
15
21
 
16
22
  @CustomElementConfig({
@@ -263,10 +269,18 @@ class DisabledController extends CustomElementController {
263
269
  return true;
264
270
  }
265
271
 
266
- updated() {
267
- console.info("updated", this);
272
+ updated(property, oldValue, newValue) {
273
+ console.info("updated", { property, oldValue, newValue });
268
274
  this.#updateAttributes();
269
275
  }
276
+
277
+ hostRenderStart(isFirstRender) {
278
+ console.info("hostRenderStart", { isFirstRender, controller: this });
279
+ }
280
+
281
+ hostRenderDone(isFirstRender) {
282
+ console.info("hostRenderDone", { isFirstRender, controller: this });
283
+ }
270
284
  }
271
285
 
272
286
  @CustomElementConfig({
@@ -317,6 +331,14 @@ class PFoo extends CustomElement {
317
331
  }
318
332
  }
319
333
 
334
+ renderStart(isFirstRender) {
335
+ console.info("renderStart", { isFirstRender, tag: this.tagName });
336
+ }
337
+
338
+ renderDone(isFirstRender) {
339
+ console.info("renderDone", { isFirstRender, tag: this.tagName });
340
+ }
341
+
320
342
  render = () => {
321
343
  return <div classes={{ foo: true, "foo__disabled": this.disabledController.disabled }}>
322
344
  <div >Hello {this.name} {this.nickName ? <span> ({this.nickName})</span> : null}</div>