@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 +10 -26
- package/index.d.ts +8 -8
- package/index.js +41 -63
- package/package.json +1 -1
- package/skills/element-relocator/SKILL.md +19 -34
- package/web-types.json +4 -5
package/README.md
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# @trunkjs/element-relocator
|
|
2
2
|
|
|
3
|
-
A small Custom Element for
|
|
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
|
|
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
|
-
|
|
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`
|
|
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 `
|
|
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
|
|
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
|
|
6
|
-
|
|
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
|
|
11
|
-
private
|
|
12
|
-
private
|
|
10
|
+
private querySelectorInDocument;
|
|
11
|
+
private observeSource;
|
|
12
|
+
private moveSourceItems;
|
|
13
13
|
private restore;
|
|
14
|
-
private
|
|
14
|
+
private disconnectSourceObserver;
|
|
15
15
|
private warn;
|
|
16
16
|
private warnAboutUnsupportedClasses;
|
|
17
17
|
}
|
package/index.js
CHANGED
|
@@ -1,96 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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.
|
|
5
|
+
super(...arguments), this.sourceElement = null, this.targetElement = null, this.sourceObserver = null;
|
|
5
6
|
}
|
|
6
7
|
static get observedAttributes() {
|
|
7
|
-
return ["class", "source", "
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
var t;
|
|
36
|
+
querySelectorInDocument(e, s) {
|
|
55
37
|
try {
|
|
56
|
-
|
|
38
|
+
const t = this.ownerDocument.querySelector(e);
|
|
39
|
+
return t || this.warn(`${s} not found: "${e}".`), t;
|
|
57
40
|
} catch {
|
|
58
|
-
return
|
|
41
|
+
return this.warn(`Invalid "${s}" selector: "${e}".`), null;
|
|
59
42
|
}
|
|
60
43
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
77
|
-
|
|
53
|
+
if (this.disconnectSourceObserver(), !(!this.sourceElement || !this.targetElement))
|
|
54
|
+
for (; this.targetElement.firstElementChild; )
|
|
55
|
+
this.sourceElement.append(this.targetElement.firstElementChild);
|
|
78
56
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
57
|
+
disconnectSourceObserver() {
|
|
58
|
+
var e;
|
|
59
|
+
(e = this.sourceObserver) == null || e.disconnect(), this.sourceObserver = null;
|
|
82
60
|
}
|
|
83
61
|
warn(e) {
|
|
84
|
-
console.warn(
|
|
62
|
+
console.warn(l, { reason: e, element: this });
|
|
85
63
|
}
|
|
86
64
|
warnAboutUnsupportedClasses() {
|
|
87
65
|
const e = Array.from(this.classList).filter(
|
|
88
|
-
(
|
|
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",
|
|
71
|
+
customElements.get("tj-element-relocator") || customElements.define("tj-element-relocator", u);
|
|
94
72
|
export {
|
|
95
|
-
|
|
73
|
+
u as TjElementRelocatorElement
|
|
96
74
|
};
|
package/package.json
CHANGED
|
@@ -1,61 +1,46 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: element-relocator
|
|
3
|
-
description: Use @trunkjs/element-relocator when
|
|
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
|
|
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
|
-
<
|
|
15
|
-
<nav
|
|
16
|
-
</
|
|
14
|
+
<nte-nav-2 id="desktop-navigation">
|
|
15
|
+
<nte-nav-item href="/">Start</nte-nav-item>
|
|
16
|
+
</nte-nav-2>
|
|
17
17
|
|
|
18
|
-
<
|
|
18
|
+
<nte-offcanvas>
|
|
19
|
+
<nte-nav-2 id="mobile-navigation"></nte-nav-2>
|
|
19
20
|
<tj-element-relocator
|
|
20
|
-
source="#navigation"
|
|
21
|
-
|
|
21
|
+
source="#desktop-navigation"
|
|
22
|
+
target="#mobile-navigation"
|
|
23
|
+
class="-lg:relocate"
|
|
22
24
|
></tj-element-relocator>
|
|
23
|
-
</
|
|
25
|
+
</nte-offcanvas>
|
|
24
26
|
</tj-responsive>
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
`source`
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
Do not implement separate resize listeners or breakpoint handling around the relocator when `@trunkjs/responsive` can express the condition.
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
## Attributes
|
|
34
36
|
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
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
|
-
|
|
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
|
|
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": "
|
|
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
|
|
14
|
+
"description": "CSS selector for the source navigation."
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
|
-
"name": "
|
|
18
|
-
"description": "
|
|
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
|
}
|