p-elements-core 1.2.2 → 1.2.3

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.
@@ -166,13 +166,21 @@ declare const CustomElementConfig: (
166
166
  config: IElementConfig
167
167
  ) => (Element: any) => void;
168
168
 
169
+ declare type ElementProjectorMode = "append" | "merge" | "replace";
170
+
169
171
  declare abstract class CustomElement extends HTMLElement {
170
- protected templateFromString(html: string, shady?: boolean): any;
171
- protected updateStyle(): void;
172
- protected createProjector(
173
- element: Element,
174
- render: () => VNode
175
- ): Promise<Projector>;
172
+ private _projector;
173
+ private _projectorMode;
174
+ private _callInitFunction;
175
+ private _initProjectorMode;
176
+ private get _supportsAdoptedStyleSheets();
177
+ private _shadowRootAdoptMapStyle;
178
+ private _shadowRootReplaceStyleWithLink;
179
+ protected templateFromString(html: string, useShadow?: boolean): any;
180
+ private _revokeAndUpdateLinkUrl;
181
+ private _getHashCode;
182
+ protected adoptStyle(root: Document | ShadowRoot, css: string): string | void;
183
+ protected createProjector(element: Element, render: () => VNode): Promise<Projector>;
176
184
  protected renderNow(): void;
177
185
  }
178
186
 
@@ -182,8 +190,6 @@ declare const Bind: (
182
190
  descriptor: any
183
191
  ) => void;
184
192
 
185
- declare function PropertyRenderOnSet(target: object, propertyKey: string): void;
186
-
187
- declare function RenderOnSet(target: object, propertyKey: string): void;
193
+ declare const PropertyRenderOnSet: (target: object, propertyKey: string) => void;
188
194
 
189
- declare function ReflectToAttribute(target: object, propertyKey: string): void;
195
+ declare const RenderOnSet: (target: object, propertyKey: string) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "p-elements-core",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "P Elements Core V1",
5
5
  "main": "dist/p-elements-core.js",
6
6
  "types": "p-elements-core.d.ts",
@@ -1,3 +1,9 @@
1
+ import { bind } from "./decorators/bind";
2
+ import { customElementConfig } from "./decorators/custom-element-config";
3
+ import { propertyRenderOnSet } from "./decorators/render-property-on-set";
4
+
5
+ export type ElementProjectorMode = "append" | "merge" | "replace";
6
+
1
7
  declare var HTMLElement: {
2
8
  prototype: HTMLElement;
3
9
  new (param?): HTMLElement;
@@ -10,112 +16,9 @@ export interface IElementConfig {
10
16
  };
11
17
  }
12
18
 
13
- export const Bind = (target, key, descriptor) => {
14
- let fn = descriptor.value;
15
- console.warn("@Bind decorator is deprecated, use arrow function expression");
16
- if (typeof fn !== "function") {
17
- throw new Error(
18
- `@Bind decorator can only be applied to methods not: ${typeof fn}`
19
- );
20
- }
21
-
22
- // In IE11 calling Object.defineProperty has a side-effect of evaluating the
23
- // getter for the property which is being replaced. This causes infinite
24
- // recursion and an "Out of stack space" error.
25
- let definingProperty = false;
26
-
27
- return {
28
- configurable: true,
29
- get() {
30
- if (
31
- definingProperty ||
32
- this === target.prototype ||
33
- this.hasOwnProperty(key) ||
34
- typeof fn !== "function"
35
- ) {
36
- return fn;
37
- }
38
-
39
- let boundFn = fn.bind(this);
40
- definingProperty = true;
41
- Object.defineProperty(this, key, {
42
- configurable: true,
43
- get() {
44
- return boundFn;
45
- },
46
- set(value) {
47
- fn = value;
48
- delete this[key];
49
- },
50
- });
51
- definingProperty = false;
52
- return boundFn;
53
- },
54
- set(value) {
55
- fn = value;
56
- },
57
- };
58
- };
59
-
60
- export const CustomElementConfig = (config: IElementConfig) => {
61
- return (Element) => {
62
- setTimeout(() => {
63
- customElements.define(config.tagName, Element, config.options);
64
- }, 10);
65
- const camelCased = config.tagName.replace(/-([a-z])/g, function (g) {
66
- return g[1].toUpperCase();
67
- });
68
- (<any>window)[camelCased.charAt(0).toUpperCase() + camelCased.slice(1)] =
69
- Element;
70
- };
71
- };
72
-
73
- export function PropertyRenderOnSet(target, key) {
74
- if (!Reflect.get(target, "_dp_" + key)) {
75
- Reflect.defineProperty(target, "_dp_" + key, {
76
- configurable: true,
77
- enumerable: true,
78
- get: function () {
79
- return this["_" + key + "_value"];
80
- },
81
- set: function (value) {
82
- this["_" + key + "_value"] = value;
83
- },
84
- });
85
- }
86
-
87
- if (!target["_dp_setters_" + key]) {
88
- target["_dp_setters_" + key] = [];
89
- }
90
-
91
- const propertyRenderOnSetFunction = (o, v) => {
92
- if (o && o.renderNow) {
93
- o.renderNow();
94
- }
95
- };
96
-
97
- target["_dp_setters_" + key].push(propertyRenderOnSetFunction);
98
-
99
- const getter = function () {
100
- return this["_" + key + "_value"];
101
- };
102
-
103
- const setter = function (newVal) {
104
- this["_dp_" + key] = newVal;
105
- if (target["_dp_setters_" + key]) {
106
- target["_dp_setters_" + key].forEach((fn) => {
107
- fn(this, newVal);
108
- });
109
- }
110
- };
111
-
112
- Object.defineProperty(target, key, {
113
- configurable: true,
114
- enumerable: true,
115
- get: getter,
116
- set: setter,
117
- });
118
- }
19
+ export const Bind = bind;
20
+ export const CustomElementConfig = customElementConfig;
21
+ export const PropertyRenderOnSet = propertyRenderOnSet;
119
22
 
120
23
  declare var HTMLElement: {
121
24
  prototype: HTMLElement;
@@ -133,45 +36,88 @@ if (!isNative((document as any).registerElement)) {
133
36
  // cssMap contain css string processed by ShadyCSS
134
37
  const cssMap = new Map();
135
38
 
39
+ // documentAdoptedStyleSheets contain hash of css string
40
+ const documentAdoptedStyleSheets: number[] = [];
41
+
136
42
  export abstract class CustomElement extends HTMLElement {
137
43
  constructor(self) {
138
44
  self = super(self);
45
+ this._callInitFunction();
46
+ this._initProjectorMode();
47
+ return self;
48
+ }
49
+
50
+ private _projector: Projector;
51
+
52
+ private _projectorMode: ElementProjectorMode;
53
+
54
+ private _callInitFunction() {
139
55
  if (typeof (this as any).init === "function") {
140
56
  (this as any).init();
141
57
  }
142
- return self;
143
58
  }
144
59
 
145
- private _projector: Projector;
60
+ private _initProjectorMode() {
61
+ const staticProjectorMode = (this.constructor as any).projectorMode;
62
+ this._projectorMode = staticProjectorMode ? staticProjectorMode : "append";
63
+ }
64
+
65
+ private get _supportsAdoptedStyleSheets(): boolean {
66
+ return (document as any).adoptedStyleSheets && CSSStyleSheet ? true : false;
67
+ }
146
68
 
147
- protected templateFromString(html: string): any {
69
+ private _shadowRootAdoptMapStyle(style: string) {
70
+ const styleSheet = new CSSStyleSheet();
71
+ // adopt stylesheet with css processed by ShadyCSS or the css match from template
72
+ (styleSheet as any).replaceSync(
73
+ cssMap.has(this.tagName) === true ? cssMap.get(this.tagName) : style
74
+ );
75
+ (this.shadowRoot as any).adoptedStyleSheets = [styleSheet];
76
+ }
77
+
78
+ private _shadowRootReplaceStyleWithLink(
79
+ html: string,
80
+ search: string,
81
+ style: string
82
+ ): string {
83
+ // add link to stylesheet with object uri from css processed by ShadyCSS or the css match from template
84
+ const blob = new Blob(
85
+ [cssMap.get(this.tagName) ? cssMap.get(this.tagName) : style],
86
+ { type: "text/css" }
87
+ );
88
+ html = html.replace(
89
+ search,
90
+ `<link rel="stylesheet" href="${URL.createObjectURL(
91
+ blob
92
+ )}" data-tmp="true">`
93
+ );
94
+ return html;
95
+ }
96
+
97
+ protected templateFromString(html: string, useShadow = true): any {
148
98
  // attach shadow dom if not already attached
149
- if (!this.shadowRoot) {
99
+ if (useShadow && !this.shadowRoot) {
150
100
  this.attachShadow({ mode: "open" });
151
101
  }
152
- this.shadowRoot.innerHTML = "";
153
- const supportReplaceCssSync = (this.shadowRoot as any).adoptedStyleSheets && CSSStyleSheet;
102
+ useShadow ? (this.shadowRoot.innerHTML = "") : (this.innerHTML = "");
154
103
 
155
- // find css style in template
104
+ // find css style in template
156
105
  let style = "<style></style>";
157
106
  const styleMatch = html.match(/<style[^>]*>([\s\S]*?)<\/style>/);
158
107
  if (styleMatch) {
159
108
  style = styleMatch[0];
160
- if (supportReplaceCssSync) {
109
+ if (useShadow && this._supportsAdoptedStyleSheets) {
161
110
  html = html.replace(styleMatch[0], "");
162
- const styleSheet = new CSSStyleSheet();
163
- // adopt stylesheet with css processed by ShadyCSS or the css match from template
164
- (styleSheet as any).replaceSync(cssMap.has(this.tagName) === true ? cssMap.get(this.tagName) : styleMatch[1]);
165
- (this.shadowRoot as any).adoptedStyleSheets = [styleSheet];
166
- } else {
167
- // add link to stylesheet with object uri from css processed by ShadyCSS or the css match from template
168
- const blob = new Blob([ cssMap.get(this.tagName) ? cssMap.get(this.tagName) : styleMatch[1]], { type: "text/css" });
169
- html = html.replace(
111
+ this._shadowRootAdoptMapStyle(styleMatch[1]);
112
+ } else if (useShadow) {
113
+ html = this._shadowRootReplaceStyleWithLink(
114
+ html,
170
115
  styleMatch[0],
171
- `<link rel="stylesheet" href="${URL.createObjectURL(
172
- blob
173
- )}" data-tmp="true">`
116
+ styleMatch[1]
174
117
  );
118
+ } else {
119
+ html = html.replace(styleMatch[0], "");
120
+ this.adoptStyle(document, styleMatch[1]);
175
121
  }
176
122
  }
177
123
 
@@ -180,12 +126,11 @@ export abstract class CustomElement extends HTMLElement {
180
126
  myElementTemplate.innerHTML = html;
181
127
  let template = document.importNode(myElementTemplate.content, true);
182
128
 
183
- // process css with ShadyCSS function
129
+ // process css with ShadyCSS function
184
130
  const textContentForShadyStyle = () => {
185
131
  if (cssMap.has(this.tagName)) {
186
132
  return cssMap.get(this.tagName);
187
133
  }
188
-
189
134
  const t = document.createElement("template");
190
135
  t.innerHTML = style;
191
136
  const p = (window as any).ShadyCSS.prepareTemplate(t, "x-tmp");
@@ -195,7 +140,7 @@ export abstract class CustomElement extends HTMLElement {
195
140
  return s;
196
141
  };
197
142
 
198
- // create object uri from css processed by ShadyCSS function
143
+ // create object uri from css processed by ShadyCSS function
199
144
  const creatObjectUrlForShadyStyle = () => {
200
145
  if (cssMap.has(this.tagName)) {
201
146
  return cssMap.get(this.tagName);
@@ -211,56 +156,92 @@ export abstract class CustomElement extends HTMLElement {
211
156
  return url;
212
157
  };
213
158
 
214
- window.addEventListener("customStyleConnected", () => {
215
- setTimeout(() => {
216
- if (styleMatch && supportReplaceCssSync) {
217
- html = html.replace(styleMatch[0], "");
218
- const styleSheet = new CSSStyleSheet();
219
- const css = textContentForShadyStyle();
220
- (styleSheet as any).replaceSync(css);
221
- (this.shadowRoot as any).adoptedStyleSheets = [styleSheet];
222
- }
223
- else {
224
- const link = this.shadowRoot.querySelector("link");
225
- if (link) {
226
- if (link.hasAttribute("data-tmp")) {
227
- URL.revokeObjectURL(link.href);
228
- link.removeAttribute("data-tmp");
229
- }
230
- link.href = creatObjectUrlForShadyStyle();
159
+ if (useShadow) {
160
+ window.addEventListener("customStyleConnected", () => {
161
+ setTimeout(() => {
162
+ if (styleMatch && this._supportsAdoptedStyleSheets) {
163
+ html = html.replace(styleMatch[0], "");
164
+ const styleSheet = new CSSStyleSheet();
165
+ const css = textContentForShadyStyle();
166
+ (styleSheet as any).replaceSync(css);
167
+ (this.shadowRoot as any).adoptedStyleSheets = [styleSheet];
168
+ } else {
169
+ this._revokeAndUpdateLinkUrl(creatObjectUrlForShadyStyle);
231
170
  }
232
- }
233
-
234
- }, 300);
235
- });
171
+ }, 300);
172
+ });
173
+ setTimeout(() => {
174
+ this._revokeAndUpdateLinkUrl(creatObjectUrlForShadyStyle);
175
+ }, 100);
176
+ }
177
+ return template;
178
+ }
236
179
 
237
- setTimeout(() => {
180
+ private _revokeAndUpdateLinkUrl(creatObjectUrlForShadyStyle: () => string) {
181
+ const link = this.shadowRoot.querySelector("link");
182
+ if (link) {
183
+ if (link.hasAttribute("data-tmp")) {
184
+ URL.revokeObjectURL(link.href);
185
+ link.removeAttribute("data-tmp");
186
+ }
187
+ link.href = creatObjectUrlForShadyStyle();
188
+ }
189
+ }
238
190
 
239
- const link = this.shadowRoot.querySelector("link");
240
- if (link) {
191
+ private _getHashCode(s: string) {
192
+ for (var i = 0, h = 0; i < s.length; i++)
193
+ h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
194
+ return h;
195
+ }
241
196
 
242
- if (link.hasAttribute("data-tmp")) {
243
- URL.revokeObjectURL(link.href);
244
- link.removeAttribute("data-tmp");
245
- }
246
- link.href = creatObjectUrlForShadyStyle();
197
+ protected adoptStyle(
198
+ root: Document | ShadowRoot,
199
+ css: string
200
+ ): string | void {
201
+ let hash = 0;
202
+ if (root instanceof Document) {
203
+ hash = this._getHashCode(css);
204
+ if (documentAdoptedStyleSheets.indexOf(hash) !== -1) {
205
+ return;
247
206
  }
207
+ }
248
208
 
249
- }, 100);
209
+ if (this._supportsAdoptedStyleSheets) {
210
+ const sheet = new CSSStyleSheet();
211
+ (sheet as any).replaceSync(css);
212
+ (root as any).adoptedStyleSheets = [
213
+ ...(root as any).adoptedStyleSheets,
214
+ sheet,
215
+ ];
216
+ } else {
217
+ const blob = new Blob([css], { type: "text/css" });
218
+ const url = URL.createObjectURL(blob);
250
219
 
251
- return template;
220
+ const link = document.createElement("link");
221
+ link.setAttribute("rel", "stylesheet");
222
+ link.setAttribute("href", url);
223
+ if (root instanceof Document) {
224
+ root.head.appendChild(link);
225
+ } else {
226
+ root.appendChild(link);
227
+ }
228
+ }
229
+ if (hash !== 0) {
230
+ documentAdoptedStyleSheets.push(hash);
231
+ }
252
232
  }
253
233
 
234
+
254
235
  protected createProjector(
255
236
  element: Element,
256
237
  render: () => VNode
257
238
  ): Promise<Projector> {
258
239
  return new Promise<Projector>((resolve, reject) => {
259
240
  let projector: Projector;
260
-
241
+ const mode = this._projectorMode;
261
242
  setTimeout(() => {
262
243
  projector = (window as any).Maquette.createProjector();
263
- projector.append(element, render);
244
+ projector[mode](element, render);
264
245
  this._projector = projector;
265
246
  resolve(projector);
266
247
  this.dispatchEvent(new CustomEvent("firstRender", {}));
@@ -273,5 +254,4 @@ export abstract class CustomElement extends HTMLElement {
273
254
  this._projector.renderNow();
274
255
  }
275
256
  }
276
-
277
257
  }
@@ -0,0 +1,46 @@
1
+ export const bind = (target, key, descriptor) => {
2
+ let fn = descriptor.value;
3
+ console.warn("@Bind decorator is deprecated, use arrow function expression");
4
+ if (typeof fn !== "function") {
5
+ throw new Error(
6
+ `@Bind decorator can only be applied to methods not: ${typeof fn}`
7
+ );
8
+ }
9
+
10
+ // In IE11 calling Object.defineProperty has a side-effect of evaluating the
11
+ // getter for the property which is being replaced. This causes infinite
12
+ // recursion and an "Out of stack space" error.
13
+ let definingProperty = false;
14
+
15
+ return {
16
+ configurable: true,
17
+ get() {
18
+ if (
19
+ definingProperty ||
20
+ this === target.prototype ||
21
+ this.hasOwnProperty(key) ||
22
+ typeof fn !== "function"
23
+ ) {
24
+ return fn;
25
+ }
26
+
27
+ let boundFn = fn.bind(this);
28
+ definingProperty = true;
29
+ Object.defineProperty(this, key, {
30
+ configurable: true,
31
+ get() {
32
+ return boundFn;
33
+ },
34
+ set(value) {
35
+ fn = value;
36
+ delete this[key];
37
+ },
38
+ });
39
+ definingProperty = false;
40
+ return boundFn;
41
+ },
42
+ set(value) {
43
+ fn = value;
44
+ },
45
+ };
46
+ };
@@ -0,0 +1,10 @@
1
+ export const customElementConfig = (config: IElementConfig) => {
2
+ return (Element) => {
3
+ customElements.define(config.tagName, Element, config.options);
4
+ const camelCased = config.tagName.replace(/-([a-z])/g, function (g) {
5
+ return g[1].toUpperCase();
6
+ });
7
+ (<any>window)[camelCased.charAt(0).toUpperCase() + camelCased.slice(1)] =
8
+ Element;
9
+ };
10
+ };
@@ -0,0 +1,46 @@
1
+ export const propertyRenderOnSet = (target, key) => {
2
+ if (!Reflect.get(target, "_dp_" + key)) {
3
+ Reflect.defineProperty(target, "_dp_" + key, {
4
+ configurable: true,
5
+ enumerable: true,
6
+ get: function () {
7
+ return this["_" + key + "_value"];
8
+ },
9
+ set: function (value) {
10
+ this["_" + key + "_value"] = value;
11
+ },
12
+ });
13
+ }
14
+
15
+ if (!target["_dp_setters_" + key]) {
16
+ target["_dp_setters_" + key] = [];
17
+ }
18
+
19
+ const propertyRenderOnSetFunction = (o, v) => {
20
+ if (o && o.renderNow) {
21
+ o.renderNow();
22
+ }
23
+ };
24
+
25
+ target["_dp_setters_" + key].push(propertyRenderOnSetFunction);
26
+
27
+ const getter = function () {
28
+ return this["_" + key + "_value"];
29
+ };
30
+
31
+ const setter = function (newVal) {
32
+ this["_dp_" + key] = newVal;
33
+ if (target["_dp_setters_" + key]) {
34
+ target["_dp_setters_" + key].forEach((fn) => {
35
+ fn(this, newVal);
36
+ });
37
+ }
38
+ };
39
+
40
+ Object.defineProperty(target, key, {
41
+ configurable: true,
42
+ enumerable: true,
43
+ get: getter,
44
+ set: setter,
45
+ });
46
+ }
package/src/sample.tsx CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  PropertyRenderOnSet,
3
3
  CustomElement,
4
4
  CustomElementConfig,
5
- Bind,
5
+ ElementProjectorMode,
6
6
  } from "./custom-element";
7
7
  import * as anime from "animejs";
8
8
 
@@ -199,16 +199,21 @@ class MyGreetings extends CustomElement {
199
199
  tagName: "p-foo",
200
200
  })
201
201
  class PFoo extends CustomElement {
202
- private connectedCallback() {
202
+ constructor(args) {
203
+ super(args);
203
204
  const template = this.templateFromString(
204
- `<style>:host{color: red}</style><div class="root"></div>`
205
+ `<style>.foo{color: red}</style><div></div>`, false
205
206
  );
206
- this.shadowRoot.appendChild(template);
207
-
208
- this.shadowRoot.querySelector(".root").innerHTML = `
209
- <div>P-FOO</div>
210
- `;
207
+ this.appendChild(template);
208
+ this.createProjector(this.querySelector("div"), this.render);
211
209
  }
210
+
211
+ static projectorMode: ElementProjectorMode = "replace";
212
+
213
+ private render = () => {
214
+ return <div class="foo">Foo</div>;
215
+ };
216
+
212
217
  }
213
218
 
214
219
  class SuperAnchorElement extends HTMLAnchorElement {