UNPKG

27.4 kBPlain TextView Raw
1/**
2 * @license
3 * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4 * This code may only be used under the BSD style license found at
5 * http://polymer.github.io/LICENSE.txt
6 * The complete set of authors may be found at
7 * http://polymer.github.io/AUTHORS.txt
8 * The complete set of contributors may be found at
9 * http://polymer.github.io/CONTRIBUTORS.txt
10 * Code distributed by Google as part of the polymer project is also
11 * subject to an additional IP rights grant found at
12 * http://polymer.github.io/PATENTS.txt
13 */
14
15/**
16 * When using Closure Compiler, JSCompiler_renameProperty(property, object) is
17 * replaced at compile time by the munged name for object[property]. We cannot
18 * alias this function, so we have to use a small shim that has the same
19 * behavior when not compiling.
20 */
21const JSCompiler_renameProperty = (prop: PropertyKey, _obj: any) => prop;
22
23/**
24 * Returns the property descriptor for a property on this prototype by walking
25 * up the prototype chain. Note that we stop just before Object.prototype, which
26 * also avoids issues with Symbol polyfills (core-js, get-own-property-symbols),
27 * which create accessors for the symbols on Object.prototype.
28 */
29const 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 * Converts property values to and from attribute values.
43 */
44export interface ComplexAttributeConverter<Type = any, TypeHint = any> {
45
46 /**
47 * Function called to convert an attribute value to a property
48 * value.
49 */
50 fromAttribute?(value: string, type?: TypeHint): Type;
51
52 /**
53 * Function called to convert a property value to an attribute
54 * value.
55 */
56 toAttribute?(value: Type, type?: TypeHint): string|null;
57}
58
59type AttributeConverter<Type = any, TypeHint = any> =
60 ComplexAttributeConverter<Type>|((value: string, type?: TypeHint) => Type);
61
62/**
63 * Defines options for a property accessor.
64 */
65export 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 */
129export interface PropertyDeclarations {
130 [key: string]: PropertyDeclaration;
131}
132
133type PropertyDeclarationMap = Map<PropertyKey, PropertyDeclaration>;
134
135type AttributeMap = Map<string, PropertyKey>;
136
137export type PropertyValues = Map<PropertyKey, unknown>;
138
139export 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
169export 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 */
177export const notEqual: HasChanged = (value: unknown, old: unknown): boolean => {
178 // This ensures (old==NaN, value==NaN) always returns false
179 return old !== value && (old === old || value === value);
180};
181
182const defaultPropertyDeclaration: PropertyDeclaration = {
183 attribute : true,
184 type : String,
185 converter : defaultConverter,
186 reflect : false,
187 hasChanged : notEqual
188};
189
190const microtaskPromise = Promise.resolve(true);
191
192const STATE_HAS_UPDATED = 1;
193const STATE_UPDATE_REQUESTED = 1 << 2;
194const STATE_IS_REFLECTING_TO_ATTRIBUTE = 1 << 3;
195const STATE_IS_REFLECTING_TO_PROPERTY = 1 << 4;
196const STATE_HAS_CONNECTED = 1 << 5;
197type 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 * Base element class which manages element properties and attributes. When
203 * properties change, the `update` method is asynchronously called. This method
204 * should be supplied by subclassers to render updates as desired.
205 */
206export abstract class UpdatingElement extends HTMLElement {
207
208 /*
209 * Due to closure compiler ES6 compilation bugs, @nocollapse is required on
210 * all static methods and properties with initializers. Reference:
211 * - https://github.com/google/closure-compiler/issues/1776
212 */
213
214 /**
215 * Maps attribute names to properties; for example `foobar` attribute to
216 * `fooBar` property. Created lazily on user subclasses when finalizing the
217 * class.
218 */
219 private static _attributeToPropertyMap: AttributeMap;
220
221 /**
222 * Marks class as having finished creating properties.
223 */
224 protected static finalized = true;
225
226 /**
227 * Memoized list of all class properties, including any superclass properties.
228 * Created lazily on user subclasses when finalizing the class.
229 */
230 private static _classProperties?: PropertyDeclarationMap;
231
232 /**
233 * User-supplied object that maps property names to `PropertyDeclaration`
234 * objects containing options for configuring the property.
235 */
236 static properties: PropertyDeclarations;
237
238 /**
239 * Returns a list of attributes corresponding to the registered properties.
240 * @nocollapse
241 */
242 static get observedAttributes() {
243 // note: piggy backing on this to ensure we're _finalized.
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 * Ensures the private `_classProperties` property metadata is created.
258 * In addition to `_finalize` this is also called in `createProperty` to
259 * ensure the `@property` decorator can add property metadata.
260 */
261 /** @nocollapse */
262 private static _ensureClassProperties() {
263 // ensure private storage for property declarations.
264 if (!this.hasOwnProperty(
265 JSCompiler_renameProperty('_classProperties', this))) {
266 this._classProperties = new Map();
267 // NOTE: Workaround IE11 not supporting Map constructor argument.
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 * Creates a property accessor on the element prototype if one does not exist.
278 * The property setter calls the property's `hasChanged` property option
279 * or uses a strict identity check to determine whether or not to request
280 * an update.
281 * @nocollapse
282 */
283 static createProperty(name: PropertyKey,
284 options:
285 PropertyDeclaration = defaultPropertyDeclaration) {
286 // Note, since this can be called by the `@property` decorator which
287 // is called before `_finalize`, we ensure storage exists for property
288 // metadata.
289 this._ensureClassProperties();
290 this._classProperties!.set(name, options);
291 if (!options.noAccessor) {
292 const superDesc = descriptorFromPrototype(name, this.prototype);
293 let desc;
294 // If there is a super accessor, capture it and "super" to it
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 * Creates property accessors for registered properties and ensures
326 * any superclasses are also finalized.
327 * @nocollapse
328 */
329 private static _finalize() {
330 if (this.hasOwnProperty(JSCompiler_renameProperty('finalized', this)) &&
331 this.finalized) {
332 return;
333 }
334 // finalize any superclasses
335 const superCtor = Object.getPrototypeOf(this);
336 if (typeof superCtor._finalize === 'function') {
337 superCtor._finalize();
338 }
339 this.finalized = true;
340 this._ensureClassProperties();
341 // initialize Map populated in observedAttributes
342 this._attributeToPropertyMap = new Map();
343 // make any properties
344 // Note, only process "own" properties since this element will inherit
345 // any properties defined on the superClass, and finalization ensures
346 // the entire prototype chain is finalized.
347 if (this.hasOwnProperty(JSCompiler_renameProperty('properties', this))) {
348 const props = this.properties;
349 // support symbols in properties (IE11 does not support this)
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 // note, use of `any` is due to TypeSript lack of support for symbol in
358 // index types
359 this.createProperty(p, (props as any)[p]);
360 }
361 }
362 }
363
364 /**
365 * Returns the property name for the given attribute `name`.
366 * @nocollapse
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 * Returns true if a property should request an update.
381 * Called when a property value is set and uses the `hasChanged`
382 * option for the property if present or a strict identity check.
383 * @nocollapse
384 */
385 private static _valueHasChanged(value: unknown, old: unknown,
386 hasChanged: HasChanged = notEqual) {
387 return hasChanged(value, old);
388 }
389
390 /**
391 * Returns the property value for the given attribute value.
392 * Called via the `attributeChangedCallback` and uses the property's
393 * `converter` or `converter.fromAttribute` property option.
394 * @nocollapse
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 * Returns the attribute value for the given property value. If this
407 * returns undefined, the property will *not* be reflected to an attribute.
408 * If this returns null, the attribute will be removed, otherwise the
409 * attribute will be set to the value.
410 * This uses the property's `reflect` and `type.toAttribute` property options.
411 * @nocollapse
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 // that we'd like to discover).
538 // mark state reflecting
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 // mark state not reflecting
546 this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_ATTRIBUTE;
547 }
548 }
549
550 private _attributeToProperty(name: string, value: string) {
551 // Use tracking info to avoid deserializing attribute value if it was
552 // just set from a property setter.
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 // mark state reflecting
562 this._updateState = this._updateState | STATE_IS_REFLECTING_TO_PROPERTY;
563 this[propName as keyof this] =
564 ctor._propertyValueFromAttribute(value, options);
565 // mark state not reflecting
566 this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_PROPERTY;
567 }
568 }
569
570 /**
571 * Requests an update which is processed asynchronously. This should
572 * be called when an element should update based on some state not triggered
573 * by setting a property. In this case, pass no arguments. It should also be
574 * called when manually implementing a property setter. In this case, pass the
575 * property `name` and `oldValue` to ensure that any configured property
576 * options are honored. Returns the `updateComplete` Promise which is resolved
577 * when the update completes.
578 *
579 * @param name {PropertyKey} (optional) name of requesting property
580 * @param oldValue {any} (optional) old value of requesting property
581 * @returns {Promise} A Promise that is resolved when the update completes.
582 */
583 requestUpdate(name?: PropertyKey, oldValue?: any) {
584 let shouldRequestUpdate = true;
585 // if we have a property key, perform property update steps.
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 // track old value when changing.
593 this._changedProperties.set(name, oldValue);
594 // add to reflecting properties set
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 // abort the request if the property should not be considered changed.
603 } else {
604 shouldRequestUpdate = false;
605 }
606 }
607 if (!this._hasRequestedUpdate && shouldRequestUpdate) {
608 this._enqueueUpdate();
609 }
610 return this.updateComplete;
611 }
612
613 /**
614 * Sets up the element to asynchronously update.
615 */
616 private async _enqueueUpdate() {
617 // Mark state updating...
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 // Ensure any previous update has resolved before updating.
623 // This `await` also ensures that property changes are batched.
624 await previousUpdatePromise;
625 // Make sure the element has connected before updating.
626 if (!this._hasConnected) {
627 await new Promise((res) => this._hasConnectedResolver = res);
628 }
629 // Allow `performUpdate` to be asynchronous to enable scheduling of updates.
630 const result = this.performUpdate();
631 // Note, this is to avoid delaying an additional microtask unless we need
632 // to.
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 * Performs an element update.
652 *
653 * You can override this method to change the timing of updates. For instance,
654 * to schedule updates to occur just before the next frame:
655 *
656 * ```
657 * protected async performUpdate(): Promise<unknown> {
658 * await new Promise((resolve) => requestAnimationFrame(() => resolve()));
659 * super.performUpdate();
660 * }
661 * ```
662 */
663 protected performUpdate(): void|Promise<unknown> {
664 // Mixin instance properties once, if they exist.
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 * Returns a Promise that resolves when the element has completed updating.
689 * The Promise value is a boolean that is `true` if the element completed the
690 * update without triggering another update. The Promise result is `false` if
691 * a property was set inside `updated()`. This getter can be implemented to
692 * await additional state. For example, it is sometimes useful to await a
693 * rendered element before fulfilling this Promise. To do this, first await
694 * `super.updateComplete` then any subsequent state.
695 *
696 * @returns {Promise} The Promise returns a boolean that indicates if the
697 * update resolved without triggering another update.
698 */
699 get updateComplete() { return this._updatePromise; }
700
701 /**
702 * Controls whether or not `update` should be called when the element requests
703 * an update. By default, this method always returns `true`, but this can be
704 * customized to control when to update.
705 *
706 * * @param _changedProperties Map of changed properties with old values
707 */
708 protected shouldUpdate(_changedProperties: PropertyValues): boolean {
709 return true;
710 }
711
712 /**
713 * Updates the element. This method reflects property values to attributes.
714 * It can be overridden to render and keep updated element DOM.
715 * Setting properties inside this method will *not* trigger
716 * another update.
717 *
718 * * @param _changedProperties Map of changed properties with old values
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 * Invoked whenever the element is updated. Implement to perform
732 * post-updating tasks via DOM APIs, for example, focusing an element.
733 *
734 * Setting properties inside this method will trigger the element to update
735 * again after this update cycle completes.
736 *
737 * * @param _changedProperties Map of changed properties with old values
738 */
739 protected updated(_changedProperties: PropertyValues) {}
740
741 /**
742 * Invoked when the element is first updated. Implement to perform one time
743 * work on the element after update.
744 *
745 * Setting properties inside this method will trigger the element to update
746 * again after this update cycle completes.
747 *
748 * * @param _changedProperties Map of changed properties with old values
749 */
750 protected firstUpdated(_changedProperties: PropertyValues) {}
751}
752
\No newline at end of file