@arcgis/lumina 5.1.0-next.21 → 5.1.0-next.22

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.
@@ -5,6 +5,7 @@ import { ToElement } from './jsx/types.ts';
5
5
  import { BaseController, LuminaPropertyDeclaration } from './controllers/types.ts';
6
6
  import { ControllerManager } from './controllers/ControllerManager.ts';
7
7
  import { Controller } from './controllers/Controller.ts';
8
+ import { FormAssociatedEvents } from './formAssociatedUtils.ts';
8
9
  type ComponentLifecycle = {
9
10
  connectedCallback?: () => void;
10
11
  disconnectedCallback?: () => void;
@@ -117,6 +118,19 @@ export declare class LitElement extends OriginalLitElement implements ComponentL
117
118
  * @private
118
119
  */
119
120
  __offspringComponents: ProxyComponent["__offspringComponents"];
121
+ /**
122
+ * For HMR, if the .elementInternals was created, preserve that element
123
+ * internals instance across hot reloads. This method lets HMR get a reference
124
+ * to element internals in a side-effect free way and without adding production
125
+ * bytes.
126
+ *
127
+ * @private
128
+ */
129
+ devOnly$PureGetElementInternals?: () => ElementInternals | undefined;
130
+ /**
131
+ * @see [MDN ElementInternals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
132
+ */
133
+ get elementInternals(): ElementInternals;
120
134
  constructor();
121
135
  connectedCallback(): void;
122
136
  disconnectedCallback(): void;
@@ -157,6 +171,7 @@ export declare class LitElement extends OriginalLitElement implements ComponentL
157
171
  * ```
158
172
  */
159
173
  listen<K extends keyof HTMLElementEventMap>(name: K, listener: (this: this, event: HTMLElementEventMap[K]) => unknown, options?: AddEventListenerOptions | boolean): void;
174
+ listen<K extends keyof FormAssociatedEvents>(name: K, listener: (this: this, event: FormAssociatedEvents[K]) => unknown, options?: Omit<AddEventListenerOptions, "capture" | "passive"> | boolean): void;
160
175
  listen(name: string, listener: (this: this, event: Event) => unknown, options?: AddEventListenerOptions | boolean): void;
161
176
  listen<EventType extends Event = CustomEvent<"Provide type like this.listenOn<ToEvents<ArcgisCounter>['arcgisClick']>() to get type-checked payload type">>(name: string,
162
177
  /**
@@ -0,0 +1,49 @@
1
+ import { ProxyComponent } from './lazyLoad.ts';
2
+ import { LitElement } from './LitElement.ts';
3
+ /**
4
+ * Web platform allows custom elements to define these methods to handle the
5
+ * corresponding form events. For performance reasons, the engines require that
6
+ * these methods are present when the customElements.define() is called - it
7
+ * means that we need to include them in the lazy proxy as well.
8
+ *
9
+ * The lazy proxy today only creates the methods proxies the first time a proxy
10
+ * element of that tag name is created, which is too late.
11
+ *
12
+ * We could update the proxy to define the method proxies right away. Some cons
13
+ * with that:
14
+ * - In lazy builds, most components are never loaded, so I want to keep the
15
+ * per-proxy overhead minimal.
16
+ * - To handle this form event, the component would need to define a
17
+ * corresponding method with a \@method() decorator (to ensure it is present
18
+ * on the proxy), and the method needs to be async (so that the lazy proxy
19
+ * knows to await component loading before calling the method)
20
+ * - If you forget to add \@method(), lazy proxy won't proxy this method. If
21
+ * you forget to make it async, lazy proxy will error if the component is
22
+ * not loaded yet. These are not obvious constraints from lazy-loading, and
23
+ * do not match the examples online/copilot. We may need lint rules about
24
+ * this.
25
+ * - For async methods, the proxy by default will call the method only after this
26
+ * component (and all its children) are fully loaded. The component may wish
27
+ * to handle these events sooner than that.
28
+ * - Such API is not composable - if the method needs to be present on the
29
+ * component, it is hard for a controller to handle it, creating wiring
30
+ * boilerplate.
31
+ *
32
+ * To address these concerns, the proxy does the following:
33
+ * - Always define these methods on the prototype (harmless for
34
+ * non-form-associated components). The methods are created once, so no
35
+ * per-proxy overhead.
36
+ * - The proxy methods will wait for component constructor (not load).
37
+ * - The proxy will emit a corresponding event on itself. The component or any
38
+ * controller can choose to listen to these events. The event does not bubble
39
+ * and does not compose, so will have minimum visibility outside the component.
40
+ * - To keep the non-lazy behavior aligned, we also emit such events on itself
41
+ * in non-lazy builds.
42
+ */
43
+ export interface FormAssociatedEvents {
44
+ luminaFormAssociatedCallback: CustomEvent<readonly [form: HTMLFormElement | null]>;
45
+ luminaFormDisabledCallback: CustomEvent<readonly [disabled: boolean]>;
46
+ luminaFormResetCallback: CustomEvent<readonly []>;
47
+ luminaFormStateRestoreCallback: CustomEvent<readonly [state: unknown, mode: "autocomplete" | "restore"]>;
48
+ }
49
+ export declare function proxyFormMethodsToEvents(constructor: typeof LitElement | typeof ProxyComponent): void;
@@ -21,6 +21,7 @@ export type HmrComponentMeta = {
21
21
  readonly properties: (readonly [property: string, attribute: string] | readonly [property: string])[];
22
22
  readonly asyncMethods: readonly string[];
23
23
  readonly syncMethods: readonly string[];
24
+ readonly formAssociated: boolean;
24
25
  };
25
26
  /**
26
27
  * Update lazy component meta
@@ -1,4 +1,4 @@
1
- import { n as noShadowRoot, P as ProxyComponent } from "./lazyLoad-DJM-hmfq.js";
1
+ import { n as noShadowRoot, P as ProxyComponent } from "./lazyLoad-D1geV6yY.js";
2
2
  import { camelToKebab } from "@arcgis/toolkit/string";
3
3
  function handleHmrUpdate(newModules) {
4
4
  newModules.forEach((newModule) => {
@@ -56,6 +56,8 @@ function reInitialize(instance, newModule) {
56
56
  ([propertyName, descriptor]) => typeof propertyName === "string" && (instance.devOnly$hmrSetProps.has(propertyName) || typeof descriptor.attribute === "string" && instance.devOnly$hmrSetAttributes.has(descriptor.attribute))
57
57
  ).map(([key]) => [key, instance[key]]);
58
58
  instance.devOnly$hmrResetStore(Object.fromEntries(preservedProperties));
59
+ const elementInternals = instance.$component.devOnly$PureGetElementInternals();
60
+ instance.devOnly$elementInternals = elementInternals;
59
61
  if (instance.isConnected) {
60
62
  instance.$component.disconnectedCallback();
61
63
  }
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ import { p as propertyTrackResolve } from "./Controller-DQnXLHsP.js";
2
2
  import { c } from "./Controller-DQnXLHsP.js";
3
3
  import { state } from "lit/decorators/state.js";
4
4
  import { property as property$1 } from "lit/decorators/property.js";
5
- import { e as emptyFunction, n as noShadowRoot, a as attachToAncestor } from "./lazyLoad-DJM-hmfq.js";
6
- import { d, m } from "./lazyLoad-DJM-hmfq.js";
5
+ import { e as emptyFunction, n as noShadowRoot, p as proxyFormMethodsToEvents, a as attachToAncestor } from "./lazyLoad-D1geV6yY.js";
6
+ import { d, m } from "./lazyLoad-D1geV6yY.js";
7
7
  import { isEsriInternalEnv, safeAsyncCall, safeCall } from "@arcgis/toolkit/error";
8
8
  import { camelToKebab } from "@arcgis/toolkit/string";
9
9
  import { Deferred } from "@arcgis/toolkit/promise";
@@ -65,6 +65,10 @@ class LitElement extends LitElement$1 {
65
65
  );
66
66
  }
67
67
  });
68
+ this.devOnly$PureGetElementInternals = () => this.#elementInternals;
69
+ if (lazy?.devOnly$elementInternals !== void 0) {
70
+ this.#elementInternals = lazy.devOnly$elementInternals;
71
+ }
68
72
  }
69
73
  }
70
74
  if (createObservable) {
@@ -160,6 +164,9 @@ class LitElement extends LitElement$1 {
160
164
  static {
161
165
  this.lumina = true;
162
166
  }
167
+ static {
168
+ proxyFormMethodsToEvents(this);
169
+ }
163
170
  /**
164
171
  * The JS API's Accessor observables. This is used to integrate with the JS
165
172
  * API's reactivity system.
@@ -171,6 +178,14 @@ class LitElement extends LitElement$1 {
171
178
  #enableUpdating;
172
179
  #postLoadedDeferred;
173
180
  #trackingTarget;
181
+ #elementInternals;
182
+ /**
183
+ * @see [MDN ElementInternals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
184
+ */
185
+ get elementInternals() {
186
+ this.#elementInternals ??= this.el.attachInternals();
187
+ return this.#elementInternals;
188
+ }
174
189
  connectedCallback() {
175
190
  if (this.el.hasAttribute("defer-hydration")) {
176
191
  return;
@@ -178,7 +178,7 @@ interface JsxNodeArray extends Array<JsxNode> {
178
178
  * Do not exclude "manager" and "componentOnReady" since these properties are
179
179
  * available both on lazy and non-lazy instance.
180
180
  */
181
- type ExcludedProperties = "addController" | "attributeChangedCallback" | "connectedCallback" | "disconnectedCallback" | "el" | "hasUpdated" | "isUpdatePending" | "listen" | "load" | "loaded" | "removeController" | "render" | "renderOptions" | "renderRoot" | "requestUpdate" | "updateComplete" | "updated" | "willUpdate";
181
+ type ExcludedProperties = "addController" | "attributeChangedCallback" | "connectedCallback" | "disconnectedCallback" | "el" | "elementInternals" | "hasUpdated" | "isUpdatePending" | "listen" | "load" | "loaded" | "removeController" | "render" | "renderOptions" | "renderRoot" | "requestUpdate" | "updateComplete" | "updated" | "willUpdate";
182
182
  /**
183
183
  * this.el property on a component only has the public properties of the
184
184
  * component. All internal methods, properties, as well as LitElement methods
@@ -106,6 +106,22 @@ const devOnly$getLitElementTagNameAndRuntime = process.env.NODE_ENV !== "product
106
106
  runtime: componentClass.K
107
107
  };
108
108
  } : void 0;
109
+ function proxyFormMethodsToEvents(constructor) {
110
+ for (const namePart of ["Associated", "Disabled", "Reset", "StateRestore"]) {
111
+ const callbackNamePart = `orm${namePart}Callback`;
112
+ constructor.prototype["f" + callbackNamePart] = function(...args) {
113
+ this.manager === void 0 ? this.constructor.B.then(
114
+ () => dispatch(this, callbackNamePart, ...args)
115
+ ) : dispatch(this, callbackNamePart, ...args);
116
+ };
117
+ }
118
+ const dispatch = (component, name, ...args) => (
119
+ // Always emit on the component, not proxy so that we can listen in a consistent place
120
+ (component.el ?? component).dispatchEvent(
121
+ new CustomEvent(`luminaF${name}`, { detail: args })
122
+ )
123
+ );
124
+ }
109
125
  const makeDefineCustomElements = (runtime, structure) => function defineCustomElements(windowOrOptions, options) {
110
126
  if (!globalThis.customElements) {
111
127
  return;
@@ -115,7 +131,7 @@ const makeDefineCustomElements = (runtime, structure) => function defineCustomEl
115
131
  if (resourcesUrl) {
116
132
  runtime.setAssetPath(resourcesUrl);
117
133
  }
118
- for (const [tagName, [load, compactMeta = ""]] of Object.entries(structure)) {
134
+ for (const [tagName, [load, compactMeta = "", flags = 0]] of Object.entries(structure)) {
119
135
  if (customElements.get(tagName)) {
120
136
  continue;
121
137
  }
@@ -123,6 +139,9 @@ const makeDefineCustomElements = (runtime, structure) => function defineCustomEl
123
139
  const observedProps = compactObservedProps ? compactObservedProps?.split(lazyMetaItemJoiner).map(parseCondensedProp) : void 0;
124
140
  const observedProperties = observedProps?.map(([property]) => property);
125
141
  const ProxyClass = class extends ProxyComponent {
142
+ static {
143
+ this.formAssociated = flags === 1;
144
+ }
126
145
  static {
127
146
  this.observedAttributes = observedProps?.map(([, attribute]) => attribute).filter((attribute) => attribute !== "");
128
147
  }
@@ -179,6 +198,7 @@ class ProxyComponent extends HtmlElement {
179
198
  this.devOnly$hmrResetStore = (newStore) => {
180
199
  this.#store = newStore;
181
200
  };
201
+ this.devOnly$elementInternals = void 0;
182
202
  }
183
203
  const that = this;
184
204
  const ProxyClass = that.constructor;
@@ -265,6 +285,9 @@ class ProxyComponent extends HtmlElement {
265
285
  });
266
286
  }
267
287
  }
288
+ static {
289
+ proxyFormMethodsToEvents(this);
290
+ }
268
291
  #litElement;
269
292
  #store;
270
293
  #pendingAttributes;
@@ -410,5 +433,6 @@ export {
410
433
  devOnly$getLitElementTagNameAndRuntime as d,
411
434
  emptyFunction as e,
412
435
  makeDefineCustomElements as m,
413
- noShadowRoot as n
436
+ noShadowRoot as n,
437
+ proxyFormMethodsToEvents as p
414
438
  };
@@ -9,6 +9,7 @@ import { ControllerManager } from './controllers/ControllerManager.ts';
9
9
  * it will start loading the actual web component source code.
10
10
  */
11
11
  export interface DefineCustomElements {
12
+ /** @deprecated Passing the Window argument is not necessary - omit the first argument */
12
13
  (_window?: Window, options?: LazyLoadOptions): void;
13
14
  (options?: LazyLoadOptions): void;
14
15
  }
@@ -24,8 +25,15 @@ export declare const makeDefineCustomElements: (runtime: Runtime, structure: Rea
24
25
  * map-components). Also, the meta is a string to speed-up parsing
25
26
  * (browsers parse strings much faster).
26
27
  * See https://twitter.com/mathias/status/1143551692732030979
28
+ *
29
+ * - 1st item - the promise to load the component.
30
+ * - 2nd item: compact metadata string (lists attributes, properties, async
31
+ * methods, and sync methods)
32
+ * - 3rd item: flags. If present, it will be 1 and indicates that the component
33
+ * is formAssociated. We can expand this field to include more information in
34
+ * the future using bit flags.
27
35
  */
28
- type CompactMeta = readonly [load: () => Promise<Record<string, typeof LitElement>>, compact?: string];
36
+ type CompactMeta = readonly [load: () => Promise<Record<string, typeof LitElement>>, compact?: string, flags?: 1];
29
37
  /**
30
38
  * If this file is run from a Node.js environment, HTMLElement won't be defined.
31
39
  * Falling back to uselessly extending parseCondensedProp in that case
@@ -75,6 +83,7 @@ export declare abstract class ProxyComponent extends HtmlElement {
75
83
  devOnly$hmrSetAttributes: Set<string>;
76
84
  devOnly$InitializeComponent?: (module: Record<string, typeof LitElement>) => void;
77
85
  devOnly$hmrResetStore?: (newStore: Record<string, unknown>) => void;
86
+ devOnly$elementInternals?: ElementInternals;
78
87
  /**
79
88
  * This property is only set in development mode and exists only for usage in
80
89
  * tests or during debugging
@@ -70,7 +70,7 @@ export type Runtime = RuntimeOptions & {
70
70
  */
71
71
  o?: () => AccessorObservableLike;
72
72
  /**
73
- * In CDN build, we can only import @arcgis/core async, so the lazy loader will need
73
+ * In CDN build, we can only import `@arcgis/core` async, so the lazy loader will need
74
74
  * to await this promise before beginning hydration so that the reactiveUtils
75
75
  * integration modules had time to load.
76
76
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcgis/lumina",
3
- "version": "5.1.0-next.21",
3
+ "version": "5.1.0-next.22",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "csstype": "^3.1.3",
30
30
  "tslib": "^2.8.1",
31
- "@arcgis/toolkit": "~5.1.0-next.21"
31
+ "@arcgis/toolkit": "~5.1.0-next.22"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@lit/context": "^1.1.6",