1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | const JSCompiler_renameProperty = (prop: PropertyKey, _obj: any) => prop;
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | const descriptorFromPrototype = (name: PropertyKey, proto: UpdatingElement) => {
|
30 | if (name in proto) {
|
31 | while (proto !== Object.prototype) {
|
32 | if (proto.hasOwnProperty(name)) {
|
33 | return Object.getOwnPropertyDescriptor(proto, name);
|
34 | }
|
35 | proto = Object.getPrototypeOf(proto);
|
36 | }
|
37 | }
|
38 | return undefined;
|
39 | };
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | export interface ComplexAttributeConverter<Type = any, TypeHint = any> {
|
45 |
|
46 | |
47 |
|
48 |
|
49 |
|
50 | fromAttribute?(value: string, type?: TypeHint): Type;
|
51 |
|
52 | |
53 |
|
54 |
|
55 |
|
56 | toAttribute?(value: Type, type?: TypeHint): string|null;
|
57 | }
|
58 |
|
59 | type AttributeConverter<Type = any, TypeHint = any> =
|
60 | ComplexAttributeConverter<Type>|((value: string, type?: TypeHint) => Type);
|
61 |
|
62 | /**
|
63 | * Defines options for a property accessor.
|
64 | */
|
65 | export interface PropertyDeclaration<Type = any, TypeHint = any> {
|
66 |
|
67 | /**
|
68 | * Indicates how and whether the property becomes an observed attribute.
|
69 | * If the value is `false`, the property is not added to `observedAttributes`.
|
70 | * If true or absent, the lowercased property name is observed (e.g. `fooBar`
|
71 | * becomes `foobar`). If a string, the string value is observed (e.g
|
72 | * `attribute: 'foo-bar'`).
|
73 | */
|
74 | attribute?: boolean|string;
|
75 |
|
76 | /**
|
77 | * Indicates the type of the property. This is used only as a hint for the
|
78 | * `converter` to determine how to convert the attribute
|
79 | * to/from a property.
|
80 | */
|
81 | type?: TypeHint;
|
82 |
|
83 | /**
|
84 | * Indicates how to convert the attribute to/from a property. If this value
|
85 | * is a function, it is used to convert the attribute value a the property
|
86 | * value. If it's an object, it can have keys for `fromAttribute` and
|
87 | * `toAttribute`. If no `toAttribute` function is provided and
|
88 | * `reflect` is set to `true`, the property value is set directly to the
|
89 | * attribute. A default `converter` is used if none is provided; it supports
|
90 | * `Boolean`, `String`, `Number`, `Object`, and `Array`. Note,
|
91 | * when a property changes and the converter is used to update the attribute,
|
92 | * the property is never updated again as a result of the attribute changing,
|
93 | * and vice versa.
|
94 | */
|
95 | converter?: AttributeConverter<Type, TypeHint>;
|
96 |
|
97 | /**
|
98 | * Indicates if the property should reflect to an attribute.
|
99 | * If `true`, when the property is set, the attribute is set using the
|
100 | * attribute name determined according to the rules for the `attribute`
|
101 | * property option and the value of the property converted using the rules
|
102 | * from the `converter` property option.
|
103 | */
|
104 | reflect?: boolean;
|
105 |
|
106 | /**
|
107 | * A function that indicates if a property should be considered changed when
|
108 | * it is set. The function should take the `newValue` and `oldValue` and
|
109 | * return `true` if an update should be requested.
|
110 | */
|
111 | hasChanged?(value: Type, oldValue: Type): boolean;
|
112 |
|
113 | /**
|
114 | * Indicates whether an accessor will be created for this property. By
|
115 | * default, an accessor will be generated for this property that requests an
|
116 | * update when set. If this flag is `true`, no accessor will be created, and
|
117 | * it will be the user's responsibility to call
|
118 | * `this.requestUpdate(propertyName, oldValue)` to request an update when
|
119 | * the property changes.
|
120 | */
|
121 | noAccessor?: boolean;
|
122 | }
|
123 |
|
124 | /**
|
125 | * Map of properties to PropertyDeclaration options. For each property an
|
126 | * accessor is made, and the property is processed according to the
|
127 | * PropertyDeclaration options.
|
128 | */
|
129 | export interface PropertyDeclarations {
|
130 | [key: string]: PropertyDeclaration;
|
131 | }
|
132 |
|
133 | type PropertyDeclarationMap = Map<PropertyKey, PropertyDeclaration>;
|
134 |
|
135 | type AttributeMap = Map<string, PropertyKey>;
|
136 |
|
137 | export type PropertyValues = Map<PropertyKey, unknown>;
|
138 |
|
139 | export const defaultConverter: ComplexAttributeConverter = {
|
140 |
|
141 | toAttribute(value: any, type?: any) {
|
142 | switch (type) {
|
143 | case Boolean:
|
144 | return value ? '' : null;
|
145 | case Object:
|
146 | case Array:
|
147 | // if the value is `null` or `undefined` pass this through
|
148 | // to allow removing/no change behavior.
|
149 | return value == null ? value : JSON.stringify(value);
|
150 | }
|
151 | return value;
|
152 | },
|
153 |
|
154 | fromAttribute(value: any, type?: any) {
|
155 | switch (type) {
|
156 | case Boolean:
|
157 | return value !== null;
|
158 | case Number:
|
159 | return value === null ? null : Number(value);
|
160 | case Object:
|
161 | case Array:
|
162 | return JSON.parse(value);
|
163 | }
|
164 | return value;
|
165 | }
|
166 |
|
167 | };
|
168 |
|
169 | export interface HasChanged {
|
170 | (value: unknown, old: unknown): boolean;
|
171 | }
|
172 |
|
173 | /**
|
174 | * Change function that returns true if `value` is different from `oldValue`.
|
175 | * This method is used as the default for a property's `hasChanged` function.
|
176 | */
|
177 | export const notEqual: HasChanged = (value: unknown, old: unknown): boolean => {
|
178 |
|
179 | return old !== value && (old === old || value === value);
|
180 | };
|
181 |
|
182 | const defaultPropertyDeclaration: PropertyDeclaration = {
|
183 | attribute : true,
|
184 | type : String,
|
185 | converter : defaultConverter,
|
186 | reflect : false,
|
187 | hasChanged : notEqual
|
188 | };
|
189 |
|
190 | const microtaskPromise = Promise.resolve(true);
|
191 |
|
192 | const STATE_HAS_UPDATED = 1;
|
193 | const STATE_UPDATE_REQUESTED = 1 << 2;
|
194 | const STATE_IS_REFLECTING_TO_ATTRIBUTE = 1 << 3;
|
195 | const STATE_IS_REFLECTING_TO_PROPERTY = 1 << 4;
|
196 | const STATE_HAS_CONNECTED = 1 << 5;
|
197 | type UpdateState = typeof STATE_HAS_UPDATED|typeof STATE_UPDATE_REQUESTED|
|
198 | typeof STATE_IS_REFLECTING_TO_ATTRIBUTE|
|
199 | typeof STATE_IS_REFLECTING_TO_PROPERTY|typeof STATE_HAS_CONNECTED;
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | export abstract class UpdatingElement extends HTMLElement {
|
207 |
|
208 | |
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 | |
215 |
|
216 |
|
217 |
|
218 |
|
219 | private static _attributeToPropertyMap: AttributeMap;
|
220 |
|
221 | |
222 |
|
223 |
|
224 | protected static finalized = true;
|
225 |
|
226 | |
227 |
|
228 |
|
229 |
|
230 | private static _classProperties?: PropertyDeclarationMap;
|
231 |
|
232 | |
233 |
|
234 |
|
235 |
|
236 | static properties: PropertyDeclarations;
|
237 |
|
238 | |
239 |
|
240 |
|
241 |
|
242 | static get observedAttributes() {
|
243 |
|
244 | this._finalize();
|
245 | const attributes = [];
|
246 | for (const [p, v] of this._classProperties!) {
|
247 | const attr = this._attributeNameForProperty(p, v);
|
248 | if (attr !== undefined) {
|
249 | this._attributeToPropertyMap.set(attr, p);
|
250 | attributes.push(attr);
|
251 | }
|
252 | }
|
253 | return attributes;
|
254 | }
|
255 |
|
256 | |
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | private static _ensureClassProperties() {
|
263 |
|
264 | if (!this.hasOwnProperty(
|
265 | JSCompiler_renameProperty('_classProperties', this))) {
|
266 | this._classProperties = new Map();
|
267 |
|
268 | const superProperties = Object.getPrototypeOf(this)._classProperties;
|
269 | if (superProperties !== undefined) {
|
270 | superProperties.forEach((v: any, k: PropertyKey) =>
|
271 | this._classProperties!.set(k, v));
|
272 | }
|
273 | }
|
274 | }
|
275 |
|
276 | |
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 | static createProperty(name: PropertyKey,
|
284 | options:
|
285 | PropertyDeclaration = defaultPropertyDeclaration) {
|
286 |
|
287 |
|
288 |
|
289 | this._ensureClassProperties();
|
290 | this._classProperties!.set(name, options);
|
291 | if (!options.noAccessor) {
|
292 | const superDesc = descriptorFromPrototype(name, this.prototype);
|
293 | let desc;
|
294 |
|
295 | if (superDesc !== undefined && (superDesc.set && superDesc.get)) {
|
296 | const {set, get} = superDesc;
|
297 | desc = {
|
298 | get() { return get.call(this); },
|
299 | set(value: any) {
|
300 | const oldValue = this[name];
|
301 | set.call(this, value);
|
302 | this.requestUpdate(name, oldValue);
|
303 | },
|
304 | configurable : true,
|
305 | enumerable : true
|
306 | };
|
307 | } else {
|
308 | const key = typeof name === 'symbol' ? Symbol() : `__${name}`;
|
309 | desc = {
|
310 | get() { return this[key]; },
|
311 | set(value: any) {
|
312 | const oldValue = this[name];
|
313 | this[key] = value;
|
314 | this.requestUpdate(name, oldValue);
|
315 | },
|
316 | configurable : true,
|
317 | enumerable : true
|
318 | };
|
319 | }
|
320 | Object.defineProperty(this.prototype, name, desc);
|
321 | }
|
322 | }
|
323 |
|
324 | |
325 |
|
326 |
|
327 |
|
328 |
|
329 | private static _finalize() {
|
330 | if (this.hasOwnProperty(JSCompiler_renameProperty('finalized', this)) &&
|
331 | this.finalized) {
|
332 | return;
|
333 | }
|
334 |
|
335 | const superCtor = Object.getPrototypeOf(this);
|
336 | if (typeof superCtor._finalize === 'function') {
|
337 | superCtor._finalize();
|
338 | }
|
339 | this.finalized = true;
|
340 | this._ensureClassProperties();
|
341 |
|
342 | this._attributeToPropertyMap = new Map();
|
343 |
|
344 |
|
345 |
|
346 |
|
347 | if (this.hasOwnProperty(JSCompiler_renameProperty('properties', this))) {
|
348 | const props = this.properties;
|
349 |
|
350 | const propKeys = [
|
351 | ...Object.getOwnPropertyNames(props),
|
352 | ...(typeof Object.getOwnPropertySymbols === 'function')
|
353 | ? Object.getOwnPropertySymbols(props)
|
354 | : []
|
355 | ];
|
356 | for (const p of propKeys) {
|
357 |
|
358 |
|
359 | this.createProperty(p, (props as any)[p]);
|
360 | }
|
361 | }
|
362 | }
|
363 |
|
364 | |
365 |
|
366 |
|
367 |
|
368 | private static _attributeNameForProperty(name: PropertyKey,
|
369 | options: PropertyDeclaration) {
|
370 | const attribute = options.attribute;
|
371 | return attribute === false
|
372 | ? undefined
|
373 | : (typeof attribute === 'string'
|
374 | ? attribute
|
375 | : (typeof name === 'string' ? name.toLowerCase()
|
376 | : undefined));
|
377 | }
|
378 |
|
379 | |
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 | private static _valueHasChanged(value: unknown, old: unknown,
|
386 | hasChanged: HasChanged = notEqual) {
|
387 | return hasChanged(value, old);
|
388 | }
|
389 |
|
390 | |
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 | private static _propertyValueFromAttribute(value: string,
|
397 | options: PropertyDeclaration) {
|
398 | const type = options.type;
|
399 | const converter = options.converter || defaultConverter;
|
400 | const fromAttribute =
|
401 | (typeof converter === 'function' ? converter : converter.fromAttribute);
|
402 | return fromAttribute ? fromAttribute(value, type) : value;
|
403 | }
|
404 |
|
405 | |
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 | private static _propertyValueToAttribute(value: unknown,
|
414 | options: PropertyDeclaration) {
|
415 | if (options.reflect === undefined) {
|
416 | return;
|
417 | }
|
418 | const type = options.type;
|
419 | const converter = options.converter;
|
420 | const toAttribute =
|
421 | converter && (converter as ComplexAttributeConverter).toAttribute ||
|
422 | defaultConverter.toAttribute;
|
423 | return toAttribute!(value, type);
|
424 | }
|
425 |
|
426 | private _updateState: UpdateState = 0;
|
427 | private _instanceProperties: PropertyValues|undefined = undefined;
|
428 | private _updatePromise: Promise<unknown> = microtaskPromise;
|
429 | private _hasConnectedResolver: (() => void)|undefined = undefined;
|
430 |
|
431 | /**
|
432 | * Map with keys for any properties that have changed since the last
|
433 | * update cycle with previous values.
|
434 | */
|
435 | private _changedProperties: PropertyValues = new Map();
|
436 |
|
437 | /**
|
438 | * Map with keys of properties that should be reflected when updated.
|
439 | */
|
440 | private _reflectingProperties: Map<PropertyKey, PropertyDeclaration>|
|
441 | undefined = undefined;
|
442 |
|
443 | constructor() {
|
444 | super();
|
445 | this.initialize();
|
446 | }
|
447 |
|
448 | /**
|
449 | * Performs element initialization. By default captures any pre-set values for
|
450 | * registered properties.
|
451 | */
|
452 | protected initialize() { this._saveInstanceProperties(); }
|
453 |
|
454 | /**
|
455 | * Fixes any properties set on the instance before upgrade time.
|
456 | * Otherwise these would shadow the accessor and break these properties.
|
457 | * The properties are stored in a Map which is played back after the
|
458 | * constructor runs. Note, on very old versions of Safari (<=9) or Chrome
|
459 | * (<=41), properties created for native platform properties like (`id` or
|
460 | * `name`) may not have default values set in the element constructor. On
|
461 | * these browsers native properties appear on instances and therefore their
|
462 | * default value will overwrite any element default (e.g. if the element sets
|
463 | * this.id = 'id' in the constructor, the 'id' will become '' since this is
|
464 | * the native platform default).
|
465 | */
|
466 | private _saveInstanceProperties() {
|
467 | for (const [p] of (this.constructor as typeof UpdatingElement)
|
468 | ._classProperties!) {
|
469 | if (this.hasOwnProperty(p)) {
|
470 | const value = this[p as keyof this];
|
471 | delete this[p as keyof this];
|
472 | if (!this._instanceProperties) {
|
473 | this._instanceProperties = new Map();
|
474 | }
|
475 | this._instanceProperties.set(p, value);
|
476 | }
|
477 | }
|
478 | }
|
479 |
|
480 | /**
|
481 | * Applies previously saved instance properties.
|
482 | */
|
483 | private _applyInstanceProperties() {
|
484 | for (const [p, v] of this._instanceProperties!) {
|
485 | (this as any)[p] = v;
|
486 | }
|
487 | this._instanceProperties = undefined;
|
488 | }
|
489 |
|
490 | connectedCallback() {
|
491 | this._updateState = this._updateState | STATE_HAS_CONNECTED;
|
492 | // Ensure connection triggers an update. Updates cannot complete before
|
493 | // connection and if one is pending connection the `_hasConnectionResolver`
|
494 | // will exist. If so, resolve it to complete the update, otherwise
|
495 | // requestUpdate.
|
496 | if (this._hasConnectedResolver) {
|
497 | this._hasConnectedResolver();
|
498 | this._hasConnectedResolver = undefined;
|
499 | } else {
|
500 | this.requestUpdate();
|
501 | }
|
502 | }
|
503 |
|
504 | /**
|
505 | * Allows for `super.disconnectedCallback()` in extensions while
|
506 | * reserving the possibility of making non-breaking feature additions
|
507 | * when disconnecting at some point in the future.
|
508 | */
|
509 | disconnectedCallback() {}
|
510 |
|
511 | /**
|
512 | * Synchronizes property values when attributes change.
|
513 | */
|
514 | attributeChangedCallback(name: string, old: string, value: string) {
|
515 | if (old !== value) {
|
516 | this._attributeToProperty(name, value);
|
517 | }
|
518 | }
|
519 |
|
520 | private _propertyToAttribute(
|
521 | name: PropertyKey, value: unknown,
|
522 | options: PropertyDeclaration = defaultPropertyDeclaration) {
|
523 | const ctor = (this.constructor as typeof UpdatingElement);
|
524 | const attr = ctor._attributeNameForProperty(name, options);
|
525 | if (attr !== undefined) {
|
526 | const attrValue = ctor._propertyValueToAttribute(value, options);
|
527 | // an undefined value does not change the attribute.
|
528 | if (attrValue === undefined) {
|
529 | return;
|
530 | }
|
531 | // Track if the property is being reflected to avoid
|
532 | // setting the property again via `attributeChangedCallback`. Note:
|
533 | // 1. this takes advantage of the fact that the callback is synchronous.
|
534 | // 2. will behave incorrectly if multiple attributes are in the reaction
|
535 | // stack at time of calling. However, since we process attributes
|
536 | // in `update` this should not be possible (or an extreme corner case
|
537 |
|
538 |
|
539 | this._updateState = this._updateState | STATE_IS_REFLECTING_TO_ATTRIBUTE;
|
540 | if (attrValue == null) {
|
541 | this.removeAttribute(attr);
|
542 | } else {
|
543 | this.setAttribute(attr, attrValue);
|
544 | }
|
545 |
|
546 | this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_ATTRIBUTE;
|
547 | }
|
548 | }
|
549 |
|
550 | private _attributeToProperty(name: string, value: string) {
|
551 |
|
552 |
|
553 | if (this._updateState & STATE_IS_REFLECTING_TO_ATTRIBUTE) {
|
554 | return;
|
555 | }
|
556 | const ctor = (this.constructor as typeof UpdatingElement);
|
557 | const propName = ctor._attributeToPropertyMap.get(name);
|
558 | if (propName !== undefined) {
|
559 | const options =
|
560 | ctor._classProperties!.get(propName) || defaultPropertyDeclaration;
|
561 |
|
562 | this._updateState = this._updateState | STATE_IS_REFLECTING_TO_PROPERTY;
|
563 | this[propName as keyof this] =
|
564 | ctor._propertyValueFromAttribute(value, options);
|
565 |
|
566 | this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_PROPERTY;
|
567 | }
|
568 | }
|
569 |
|
570 | |
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 |
|
583 | requestUpdate(name?: PropertyKey, oldValue?: any) {
|
584 | let shouldRequestUpdate = true;
|
585 |
|
586 | if (name !== undefined && !this._changedProperties.has(name)) {
|
587 | const ctor = this.constructor as typeof UpdatingElement;
|
588 | const options =
|
589 | ctor._classProperties!.get(name) || defaultPropertyDeclaration;
|
590 | if (ctor._valueHasChanged(this[name as keyof this], oldValue,
|
591 | options.hasChanged)) {
|
592 |
|
593 | this._changedProperties.set(name, oldValue);
|
594 |
|
595 | if (options.reflect === true &&
|
596 | !(this._updateState & STATE_IS_REFLECTING_TO_PROPERTY)) {
|
597 | if (this._reflectingProperties === undefined) {
|
598 | this._reflectingProperties = new Map();
|
599 | }
|
600 | this._reflectingProperties.set(name, options);
|
601 | }
|
602 |
|
603 | } else {
|
604 | shouldRequestUpdate = false;
|
605 | }
|
606 | }
|
607 | if (!this._hasRequestedUpdate && shouldRequestUpdate) {
|
608 | this._enqueueUpdate();
|
609 | }
|
610 | return this.updateComplete;
|
611 | }
|
612 |
|
613 | |
614 |
|
615 |
|
616 | private async _enqueueUpdate() {
|
617 |
|
618 | this._updateState = this._updateState | STATE_UPDATE_REQUESTED;
|
619 | let resolve: (r: boolean) => void;
|
620 | const previousUpdatePromise = this._updatePromise;
|
621 | this._updatePromise = new Promise((res) => resolve = res);
|
622 |
|
623 |
|
624 | await previousUpdatePromise;
|
625 |
|
626 | if (!this._hasConnected) {
|
627 | await new Promise((res) => this._hasConnectedResolver = res);
|
628 | }
|
629 |
|
630 | const result = this.performUpdate();
|
631 |
|
632 |
|
633 | if (result != null &&
|
634 | typeof (result as PromiseLike<unknown>).then === 'function') {
|
635 | await result;
|
636 | }
|
637 | resolve!(!this._hasRequestedUpdate);
|
638 | }
|
639 |
|
640 | private get _hasConnected() {
|
641 | return (this._updateState & STATE_HAS_CONNECTED);
|
642 | }
|
643 |
|
644 | private get _hasRequestedUpdate() {
|
645 | return (this._updateState & STATE_UPDATE_REQUESTED);
|
646 | }
|
647 |
|
648 | protected get hasUpdated() { return (this._updateState & STATE_HAS_UPDATED); }
|
649 |
|
650 | |
651 |
|
652 |
|
653 |
|
654 |
|
655 |
|
656 |
|
657 |
|
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 | protected performUpdate(): void|Promise<unknown> {
|
664 |
|
665 | if (this._instanceProperties) {
|
666 | this._applyInstanceProperties();
|
667 | }
|
668 | if (this.shouldUpdate(this._changedProperties)) {
|
669 | const changedProperties = this._changedProperties;
|
670 | this.update(changedProperties);
|
671 | this._markUpdated();
|
672 | if (!(this._updateState & STATE_HAS_UPDATED)) {
|
673 | this._updateState = this._updateState | STATE_HAS_UPDATED;
|
674 | this.firstUpdated(changedProperties);
|
675 | }
|
676 | this.updated(changedProperties);
|
677 | } else {
|
678 | this._markUpdated();
|
679 | }
|
680 | }
|
681 |
|
682 | private _markUpdated() {
|
683 | this._changedProperties = new Map();
|
684 | this._updateState = this._updateState & ~STATE_UPDATE_REQUESTED;
|
685 | }
|
686 |
|
687 | |
688 |
|
689 |
|
690 |
|
691 |
|
692 |
|
693 |
|
694 |
|
695 |
|
696 |
|
697 |
|
698 |
|
699 | get updateComplete() { return this._updatePromise; }
|
700 |
|
701 | |
702 |
|
703 |
|
704 |
|
705 |
|
706 |
|
707 |
|
708 | protected shouldUpdate(_changedProperties: PropertyValues): boolean {
|
709 | return true;
|
710 | }
|
711 |
|
712 | |
713 |
|
714 |
|
715 |
|
716 |
|
717 |
|
718 |
|
719 |
|
720 | protected update(_changedProperties: PropertyValues) {
|
721 | if (this._reflectingProperties !== undefined &&
|
722 | this._reflectingProperties.size > 0) {
|
723 | for (const [k, v] of this._reflectingProperties) {
|
724 | this._propertyToAttribute(k, this[k as keyof this], v);
|
725 | }
|
726 | this._reflectingProperties = undefined;
|
727 | }
|
728 | }
|
729 |
|
730 | |
731 |
|
732 |
|
733 |
|
734 |
|
735 |
|
736 |
|
737 |
|
738 |
|
739 | protected updated(_changedProperties: PropertyValues) {}
|
740 |
|
741 | |
742 |
|
743 |
|
744 |
|
745 |
|
746 |
|
747 |
|
748 |
|
749 |
|
750 | protected firstUpdated(_changedProperties: PropertyValues) {}
|
751 | }
|
752 |
|
\ | No newline at end of file |