@collagejs/core 0.5.0 → 0.6.0

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.
@@ -1,4 +1,4 @@
1
- import type { CorePiece, MountPiece, AcceptableTarget, CorePieceCapabilities } from "./types.js";
1
+ import type { CorePiece, MountPiece, AcceptableTarget, CorePieceCapabilities, RelocationResult } from "./types.js";
2
2
  export declare const mountKey: unique symbol;
3
3
  export declare class MountedPiece<TProps extends Record<string, any> = Record<string, any>, TCap extends Record<string, any> = {}> {
4
4
  #private;
@@ -7,5 +7,6 @@ export declare class MountedPiece<TProps extends Record<string, any> = Record<st
7
7
  [mountKey](target: AcceptableTarget, props?: TProps): Promise<void>;
8
8
  unmount(): Promise<void>;
9
9
  update(props: TProps): Promise<void>;
10
+ relocate(target: AcceptableTarget, newTarget: AcceptableTarget): Promise<RelocationResult>;
10
11
  get capabilities(): (CorePieceCapabilities & TCap) | undefined;
11
12
  }
@@ -30,6 +30,27 @@ async function doUpdate(update, props) {
30
30
  }
31
31
  return await update(props);
32
32
  }
33
+ async function doRelocate(relocate, target, newTarget) {
34
+ if (Array.isArray(relocate)) {
35
+ let first = true;
36
+ let ready = false;
37
+ for (const fn of relocate) {
38
+ const r = await doRelocate(fn, target, newTarget);
39
+ if (r === 'ready') {
40
+ ready = true;
41
+ }
42
+ else if (!r) {
43
+ if (first) {
44
+ return false;
45
+ }
46
+ throw new Error("Relocation function returned 'false' after another relocation function returned 'ready' or 'true'. The piece's state is now inconsistent.");
47
+ }
48
+ first = false;
49
+ }
50
+ return ready ? 'ready' : true;
51
+ }
52
+ return await relocate(target, newTarget);
53
+ }
33
54
  export class MountedPiece {
34
55
  #piece;
35
56
  #id;
@@ -68,6 +89,12 @@ export class MountedPiece {
68
89
  update(props) {
69
90
  return doUpdate(this.#piece.update, props);
70
91
  }
92
+ relocate(target, newTarget) {
93
+ if (!this.#piece.relocate) {
94
+ return Promise.resolve(false);
95
+ }
96
+ return doRelocate(this.#piece.relocate, target, newTarget);
97
+ }
71
98
  get capabilities() {
72
99
  return this.#piece.capabilities;
73
100
  }
package/dist/types.d.ts CHANGED
@@ -20,6 +20,19 @@ export type UnmountFn = () => Promise<void>;
20
20
  * @returns A promise to the cleanup function that unmounts the piece.
21
21
  */
22
22
  export type MountFn<TProps extends Record<string, any> = Record<string, any>> = (target: AcceptableTarget, props?: MountProps<TProps>) => Promise<UnmountFn>;
23
+ /**
24
+ * Defines the possible return values of `CorePiece.relocate`.
25
+ */
26
+ export type RelocationResult = boolean | 'ready';
27
+ /**
28
+ * Type that defines the signature of the functions accepted in `CorePiece.relocate`.
29
+ * @param parent The current parent of the piece's root element(s).
30
+ * @param newParent The new parent where the piece's root element(s) will be relocated.
31
+ * @returns A promise that resolves to `true` if the relocation was successful, `false` if it failed or relocation
32
+ * is disallowed, or `'ready'` if the piece is ready to be relocated, but the relocation must be performed by the
33
+ * caller.
34
+ */
35
+ export type RelocateFn = (parent: AcceptableTarget, newParent: AcceptableTarget) => Promise<RelocationResult>;
23
36
  /**
24
37
  * Type that defines the signature of the functions accepted in `CorePiece.update`.
25
38
  * @param props The new property values for the mounted piece.
@@ -33,7 +46,11 @@ export type Mount<TProps extends Record<string, any> = Record<string, any>> = Mo
33
46
  /**
34
47
  * Defines the accepted shapes for `CorePiece.update`.
35
48
  */
36
- export type Update<TProps extends Record<string, any> = Record<string, any>> = UpdateFn<TProps> | UpdateFn<TProps>[] | Update[];
49
+ export type Update<TProps extends Record<string, any> = Record<string, any>> = UpdateFn<TProps> | UpdateFn<TProps>[] | Update<TProps>[];
50
+ /**
51
+ * Defines the accepted shapes for `CorePiece.relocate`.
52
+ */
53
+ export type Relocate = RelocateFn | RelocateFn[] | Relocate[];
37
54
  /**
38
55
  * Defines the capabilities of a `CorePiece` object recognized by the core *CollageJS* library. These capabilities are
39
56
  * used to determine how the core library should handle the piece's lifecycle, or whether a particular action or
@@ -50,18 +67,6 @@ export type CorePieceCapabilities = {
50
67
  * **💡TIP**: Official framework adapters provide this functionality.
51
68
  */
52
69
  remountable?: boolean;
53
- /**
54
- * Indicates that the piece allows relocation of its HTML markup to a new parent without unmounting. For the best
55
- * development experience, piece objects should always strive to be relocatable.
56
- *
57
- * If `false`, `Piece` components created with official framework adapters will most likely have to ask HMR to
58
- * perform a full page reload whenever the developer changes shadow DOM options, like moving from an open to a
59
- * closed shadow root.
60
- *
61
- * If `true` **and if the framework is capable** (i. e. Svelte), the piece can be relocated without unmounting, and
62
- * HMR will be able to update the shadow root options without a full page reload.
63
- */
64
- relocatable?: boolean;
65
70
  };
66
71
  /**
67
72
  * Defines the contract that objects must follow in order to be mountable as *CollageJS* pieces (micro-frontends).
@@ -77,14 +82,47 @@ export interface CorePiece<TProps extends Record<string, any> = Record<string, a
77
82
  * while mounted in the document, and all property values must have been passed during mounting.
78
83
  */
79
84
  update?: Update<TProps>;
85
+ /**
86
+ * Either relocates the root element(s) of the piece to a new parent, or prepares the piece for relocation. This
87
+ * is optional. If not provided, the piece won't support relocation of its root element(s) to a new parent, and
88
+ * the piece will be unmounted and remounted instead in the pertinent cases.
89
+ *
90
+ * ### Return Values
91
+ *
92
+ * + `true`: The relocation was successful.
93
+ * + `false`: The relocation failed or is disallowed.
94
+ * + `'ready'`: The piece is ready to be relocated, but the relocation must be performed by the caller.
95
+ *
96
+ * ### When Using Multiple Relocation Functions
97
+ *
98
+ * If multiple relocation functions are provided, they will be called in order:
99
+ *
100
+ * + If the first of them returns `false`, the relocation process will stop.
101
+ * + If all of them return `true`, the relocation is considered successful.
102
+ * + If any of them returns `'ready'`, the remaining functions are still called, but the caller will
103
+ * run its relocation process after all functions have been called.
104
+ * + If any of them returns `false` after one or more of them returned `'ready'` or `true`, an error is thrown.
105
+ */
106
+ relocate?: Relocate;
80
107
  /**
81
108
  * Declares the capabilities of the piece. This is optional. If not provided, the piece will be assumed to have no
82
- * capabilities, and the core library will treat it as a simple piece that can be mounted once and unmounted once, and
83
- * that cannot be relocated or re-mounted.
109
+ * capabilities.
110
+ *
111
+ * ### Notable Exception
84
112
  *
85
- * **💡TIP**: Always try to at least create pieces that are relocatable by not storing the original target element in
86
- * the piece's state. Instead, just use the piece's root element's `parentElement` property to determine the
87
- * current parent element.
113
+ * The library defines the `remountable` capability, which is informative only. The core library cannot enforce
114
+ * this capability, so `CorePiece` developers should use the `preventRemount()` function (or equivalent) to throw an
115
+ * error if the piece is mounted more than once for pieces that cannot withstand multiple mountings.
116
+ *
117
+ * Official framework adapters query the value of `capabilities.remountable` and act accordingly to their best
118
+ * ability, and at least emit a warning if a non-remountable piece is mounted more than once.
119
+ *
120
+ * Only the author of a `CorePiece` object can guarantee that the piece is remountable. We encourage developers to
121
+ * be explicit about this capability when creating `CorePiece` objects.
122
+ *
123
+ * > ℹ️ **NOTE:** Official framework adapters are free to choose which default value for `capabilities.remountable`
124
+ * > they will use while creating `CorePiece` objects when the property is not provided in order to maximize
125
+ * > framework feature usage (perhaps a framework can guarantee component state automatically, or perhaps it cannot).
88
126
  */
89
127
  readonly capabilities?: CorePieceCapabilities & TCap;
90
128
  }
@@ -100,6 +138,10 @@ export interface MountedPiece<TProps extends Record<string, any> = Record<string
100
138
  * Function used to unmount the `CorePiece` object.
101
139
  */
102
140
  unmount: UnmountFn;
141
+ /**
142
+ * Function used to relocate the `CorePiece` object to a new parent without unmounting.
143
+ */
144
+ relocate: RelocateFn;
103
145
  /**
104
146
  * The version of the global `mountPiece` function that tracks mounted children so their unmounting is
105
147
  * synchronized with this piece's unmount event.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@collagejs/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Core functionality for CollageJS.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",