@trunkjs/element-relocator 1.0.1 → 1.0.4

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.
package/README.md CHANGED
@@ -1,18 +1,20 @@
1
1
  # @trunkjs/element-relocator
2
2
 
3
- A small Custom Element for temporarily moving an existing DOM element to a responsive destination and restoring its exact original position afterwards.
3
+ A small Custom Element for moving navigation items between two responsive navigation elements.
4
4
 
5
5
  ## Usage
6
6
 
7
7
  ```html
8
8
  <tj-responsive>
9
9
  <header>
10
- <nav id="navigation">...</nav>
10
+ <nte-nav-2 id="desktop-navigation">...</nte-nav-2>
11
11
  </header>
12
12
 
13
13
  <aside>
14
+ <nte-nav-2 id="mobile-navigation"></nte-nav-2>
14
15
  <tj-element-relocator
15
- source="#navigation"
16
+ source="#desktop-navigation"
17
+ target="#mobile-navigation"
16
18
  class="md:relocate"
17
19
  ></tj-element-relocator>
18
20
  </aside>
@@ -21,32 +23,14 @@ A small Custom Element for temporarily moving an existing DOM element to a respo
21
23
 
22
24
  `tj-element-relocator` itself has no breakpoint logic. `@trunkjs/responsive` (or any other mechanism) controls whether the `relocate` class is present.
23
25
 
24
- When `relocate` appears, the element selected by `source` is moved to the relocator. When it disappears, the source is restored to its exact original DOM position using an internal comment anchor.
26
+ When `relocate` appears, the direct child navigation items selected by `source` are moved into the `target` navigation. The source navigation is empty while the target is filled. When `relocate` disappears, the items are moved back to the source navigation.
25
27
 
26
- ## Placement
28
+ Both `source` and `target` are required CSS selectors. The target should be a second, dedicated navigation element, for example a horizontal `nte-nav-2` in the header and a vertical `nte-nav-2` in an off-canvas. The source is observed so newly added navigation items are moved as well.
27
29
 
28
- `placement` controls where the source is placed relative to `<tj-element-relocator>`:
29
-
30
- - `inside` (default): source becomes a child of the relocator.
31
- - `before`: source becomes the previous sibling of the relocator.
32
- - `after`: source becomes the next sibling of the relocator.
33
-
34
- Sibling placement is useful for Web Components and slots because the relocated source remains in the relocator's parent light DOM.
35
-
36
- ```html
37
- <my-layout>
38
- <tj-element-relocator
39
- source="#actions"
40
- placement="after"
41
- class="md:relocate"
42
- ></tj-element-relocator>
43
-
44
- <div id="actions" slot="toolbar">...</div>
45
- </my-layout>
46
- ```
30
+ `placement` is no longer supported.
47
31
 
48
32
  ## Class contract
49
33
 
50
- The element observes changes to `class`, `source`, and `placement`.
34
+ The element observes changes to `class`, `source`, and `target`.
51
35
 
52
- The only supported plain class is `relocate`. Class names containing `:` are accepted as responsive expressions. Any other class name without `:` causes a `console.warn`, but does not prevent relocation.
36
+ The only supported plain class is `relocate`. Class names containing `:` are accepted as responsive expressions. Any other class name without `:` causes a `console.warn`, but does not prevent synchronization.
package/index.d.ts CHANGED
@@ -1,17 +1,17 @@
1
- export type TjElementRelocatorPlacement = 'inside' | 'before' | 'after';
2
1
  export declare class TjElementRelocatorElement extends HTMLElement {
3
2
  static get observedAttributes(): string[];
4
3
  private sourceElement;
5
- private sourceAnchor;
6
- connectedCallback(): void;
4
+ private targetElement;
5
+ private sourceObserver;
6
+ connectedCallback(): Promise<void>;
7
7
  disconnectedCallback(): void;
8
- attributeChangedCallback(): void;
8
+ attributeChangedCallback(): Promise<void>;
9
9
  private sync;
10
- private relocate;
11
- private matchesCurrentSource;
12
- private placeSource;
10
+ private querySelectorInDocument;
11
+ private observeSource;
12
+ private moveSourceItems;
13
13
  private restore;
14
- private get placement();
14
+ private disconnectSourceObserver;
15
15
  private warn;
16
16
  private warnAboutUnsupportedClasses;
17
17
  }
package/index.js CHANGED
@@ -1,96 +1,74 @@
1
- const n = "relocate", o = "<tj-element-relocator> warning";
2
- class c extends HTMLElement {
1
+ import { waitForReady as c } from "@trunkjs/browser-utils";
2
+ const n = "relocate", l = "<tj-element-relocator> warning";
3
+ class u extends HTMLElement {
3
4
  constructor() {
4
- super(...arguments), this.sourceElement = null, this.sourceAnchor = null;
5
+ super(...arguments), this.sourceElement = null, this.targetElement = null, this.sourceObserver = null;
5
6
  }
6
7
  static get observedAttributes() {
7
- return ["class", "source", "placement"];
8
+ return ["class", "source", "target"];
8
9
  }
9
- connectedCallback() {
10
- this.sync();
10
+ async connectedCallback() {
11
+ await c(), this.sync();
11
12
  }
12
13
  disconnectedCallback() {
13
- this.restore();
14
+ this.restore(), this.sourceElement = null, this.targetElement = null;
14
15
  }
15
- attributeChangedCallback() {
16
- this.isConnected && this.sync();
16
+ async attributeChangedCallback() {
17
+ await c(), this.isConnected && this.sync();
17
18
  }
18
19
  sync() {
19
- this.warnAboutUnsupportedClasses(), this.classList.contains(n) ? this.relocate() : this.restore();
20
- }
21
- relocate() {
22
- var t;
23
- const e = (t = this.getAttribute("source")) == null ? void 0 : t.trim();
24
- if (!e) {
25
- this.warn('Missing "source" selector.');
20
+ var i, o;
21
+ this.warnAboutUnsupportedClasses();
22
+ const e = (i = this.getAttribute("source")) == null ? void 0 : i.trim(), s = (o = this.getAttribute("target")) == null ? void 0 : o.trim();
23
+ if (!e || !s) {
24
+ this.warn(`Missing ${e ? '"target"' : '"source"'} selector.`);
26
25
  return;
27
26
  }
28
- if (this.sourceElement && !this.matchesCurrentSource(e) && this.restore(), !this.sourceElement) {
29
- let r;
30
- try {
31
- r = this.ownerDocument.querySelector(e);
32
- } catch {
33
- this.warn(`Invalid "source" selector: "${e}".`);
34
- return;
35
- }
36
- if (!r) {
37
- this.warn(`Source not found: "${e}".`);
38
- return;
39
- }
40
- if (r === this || r.contains(this)) {
41
- this.warn("Source is the relocator or one of its ancestors.");
27
+ const t = this.querySelectorInDocument(e, "source"), r = this.querySelectorInDocument(s, "target");
28
+ if (!(!t || !r)) {
29
+ if (t === r || t.contains(r) || r.contains(t)) {
30
+ this.warn("Source and target must be different elements and must not contain each other.");
42
31
  return;
43
32
  }
44
- const s = r.parentNode;
45
- if (!s) {
46
- this.warn(`Source is detached: "${e}".`);
47
- return;
48
- }
49
- this.sourceAnchor = this.ownerDocument.createComment("tj-element-relocator:source"), s.insertBefore(this.sourceAnchor, r), this.sourceElement = r;
33
+ (t !== this.sourceElement || r !== this.targetElement) && (this.restore(), this.sourceElement = t, this.targetElement = r), this.classList.contains(n) ? (this.observeSource(), this.moveSourceItems()) : this.restore();
50
34
  }
51
- this.placeSource();
52
35
  }
53
- matchesCurrentSource(e) {
54
- var t;
36
+ querySelectorInDocument(e, s) {
55
37
  try {
56
- return ((t = this.sourceElement) == null ? void 0 : t.matches(e)) ?? !1;
38
+ const t = this.ownerDocument.querySelector(e);
39
+ return t || this.warn(`${s} not found: "${e}".`), t;
57
40
  } catch {
58
- return !1;
41
+ return this.warn(`Invalid "${s}" selector: "${e}".`), null;
59
42
  }
60
43
  }
61
- placeSource() {
62
- if (this.sourceElement)
63
- switch (this.placement) {
64
- case "before":
65
- this.before(this.sourceElement);
66
- break;
67
- case "after":
68
- this.after(this.sourceElement);
69
- break;
70
- case "inside":
71
- this.append(this.sourceElement);
72
- break;
73
- }
44
+ observeSource() {
45
+ this.sourceObserver || !this.sourceElement || (this.sourceObserver = new MutationObserver(() => this.moveSourceItems()), this.sourceObserver.observe(this.sourceElement, { childList: !0 }));
46
+ }
47
+ moveSourceItems() {
48
+ if (!(!this.sourceElement || !this.targetElement || !this.classList.contains(n)))
49
+ for (; this.sourceElement.firstElementChild; )
50
+ this.targetElement.append(this.sourceElement.firstElementChild);
74
51
  }
75
52
  restore() {
76
- var e, t;
77
- this.sourceElement && ((e = this.sourceAnchor) != null && e.parentNode) && this.sourceAnchor.parentNode.insertBefore(this.sourceElement, this.sourceAnchor.nextSibling), (t = this.sourceAnchor) == null || t.remove(), this.sourceAnchor = null, this.sourceElement = null;
53
+ if (this.disconnectSourceObserver(), !(!this.sourceElement || !this.targetElement))
54
+ for (; this.targetElement.firstElementChild; )
55
+ this.sourceElement.append(this.targetElement.firstElementChild);
78
56
  }
79
- get placement() {
80
- const e = this.getAttribute("placement");
81
- return e === "before" || e === "after" || e === "inside" ? e : (e && this.warn(`Unsupported placement: "${e}".`), "inside");
57
+ disconnectSourceObserver() {
58
+ var e;
59
+ (e = this.sourceObserver) == null || e.disconnect(), this.sourceObserver = null;
82
60
  }
83
61
  warn(e) {
84
- console.warn(o, { reason: e, element: this });
62
+ console.warn(l, { reason: e, element: this });
85
63
  }
86
64
  warnAboutUnsupportedClasses() {
87
65
  const e = Array.from(this.classList).filter(
88
- (t) => t !== n && !t.includes(":")
66
+ (s) => s !== n && !s.includes(":")
89
67
  );
90
68
  e.length && this.warn(`Unsupported classes: ${e.join(", ")}.`);
91
69
  }
92
70
  }
93
- customElements.get("tj-element-relocator") || customElements.define("tj-element-relocator", c);
71
+ customElements.get("tj-element-relocator") || customElements.define("tj-element-relocator", u);
94
72
  export {
95
- c as TjElementRelocatorElement
73
+ u as TjElementRelocatorElement
96
74
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trunkjs/element-relocator",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "main": "./index.js",
5
5
  "repository": {
6
6
  "directory": "packages/element-relocator",
@@ -1,61 +1,46 @@
1
1
  ---
2
2
  name: element-relocator
3
- description: Use @trunkjs/element-relocator when an existing DOM element must move to another responsive layout position and later return to its exact original position, including Web Component slot and light-DOM sibling scenarios.
3
+ description: Use @trunkjs/element-relocator when navigation items must be synchronized from one responsive navigation to another.
4
4
  ---
5
5
 
6
6
  # TrunkJS Element Relocator
7
7
 
8
- Use `<tj-element-relocator>` to move an existing element identified by a CSS selector. The relocator does not implement breakpoints itself. Prefer `@trunkjs/responsive` to control when the plain `relocate` class is present.
8
+ Use `<tj-element-relocator>` to move direct navigation-item children between two elements identified by CSS selectors. The relocator does not implement breakpoints itself. Prefer `@trunkjs/responsive` to control when the plain `relocate` class is present.
9
9
 
10
10
  ## Basic usage
11
11
 
12
12
  ```html
13
13
  <tj-responsive>
14
- <header>
15
- <nav id="navigation">...</nav>
16
- </header>
14
+ <nte-nav-2 id="desktop-navigation">
15
+ <nte-nav-item href="/">Start</nte-nav-item>
16
+ </nte-nav-2>
17
17
 
18
- <aside>
18
+ <nte-offcanvas>
19
+ <nte-nav-2 id="mobile-navigation"></nte-nav-2>
19
20
  <tj-element-relocator
20
- source="#navigation"
21
- class="md:relocate"
21
+ source="#desktop-navigation"
22
+ target="#mobile-navigation"
23
+ class="-lg:relocate"
22
24
  ></tj-element-relocator>
23
- </aside>
25
+ </nte-offcanvas>
24
26
  </tj-responsive>
25
27
  ```
26
28
 
27
- `source` identifies the element being moved. When the relocator has the plain `relocate` class, the source is moved. When `relocate` disappears, the source is restored to its exact original DOM position.
29
+ `source` and `target` are both required CSS selectors. When the relocator has the plain `relocate` class, the source's direct navigation-item children are moved into the target. The source navigation is empty while the target is filled, so the header and off-canvas never show the same items simultaneously.
28
30
 
29
- Do not implement separate resize listeners or breakpoint handling around the relocator when `@trunkjs/responsive` can express the condition.
31
+ When `relocate` disappears, the items are moved back to the source. Changes to the source child list are moved while relocation is active. The target should be a dedicated, initially empty destination.
30
32
 
31
- ## Placement
33
+ Do not implement separate resize listeners or breakpoint handling around the relocator when `@trunkjs/responsive` can express the condition.
32
34
 
33
- Use `placement` to choose where the source is inserted relative to the relocator:
35
+ ## Attributes
34
36
 
35
- - `inside` (default): the source becomes a child of `<tj-element-relocator>`.
36
- - `before`: the source becomes its previous sibling.
37
- - `after`: the source becomes its next sibling.
37
+ - `source`: required CSS selector for the source navigation.
38
+ - `target`: required CSS selector for the destination navigation.
38
39
 
39
- Prefer `inside` for ordinary DOM relocation. Use `before` or `after` when the source must remain in the surrounding component's light DOM, especially for slot assignment.
40
-
41
- ```html
42
- <my-layout>
43
- <tj-element-relocator
44
- source="#actions"
45
- placement="after"
46
- class="md:relocate"
47
- ></tj-element-relocator>
48
-
49
- <div id="actions" slot="toolbar">...</div>
50
- </my-layout>
51
- ```
52
-
53
- The `slot` attribute remains on the source; sibling placement keeps it a direct light-DOM child of `<my-layout>`.
40
+ `placement` is not supported. The relocator moves navigation items between the two selected elements instead of moving either navigation element or relying on slot reassignment.
54
41
 
55
42
  ## Class contract
56
43
 
57
44
  Treat the relocator's classes as control input, not as general styling classes.
58
45
 
59
- The only supported plain class is `relocate`. Responsive expressions containing `:` are allowed, for example `md:relocate`. Do not add unrelated plain classes such as `hidden`, `toolbar`, or `mobile`; they produce a developer warning.
60
-
61
- For implementation details and invariants, see [`ARCHITECTURE.md`](../../ARCHITECTURE.md).
46
+ The only supported plain class is `relocate`. Responsive expressions containing `:` are allowed, for example `-lg:relocate`. Do not add unrelated plain classes; they produce a developer warning.
package/web-types.json CHANGED
@@ -7,16 +7,15 @@
7
7
  "elements": [
8
8
  {
9
9
  "name": "tj-element-relocator",
10
- "description": "Relocates a source element when the 'relocate' class is present and restores it to its original DOM position when the class is removed.",
10
+ "description": "Moves navigation items from a source navigation to a target navigation while the 'relocate' class is present.",
11
11
  "attributes": [
12
12
  {
13
13
  "name": "source",
14
- "description": "CSS selector for the element to relocate."
14
+ "description": "CSS selector for the source navigation."
15
15
  },
16
16
  {
17
- "name": "placement",
18
- "description": "Placement relative to the relocator.",
19
- "value": { "type": "enum", "items": ["inside", "before", "after"] }
17
+ "name": "target",
18
+ "description": "CSS selector for the target navigation."
20
19
  }
21
20
  ]
22
21
  }