@trunkjs/content-pane 1.0.1 → 1.0.2
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/CHANGELOG.md +4 -0
- package/README.md +59 -191
- package/index.d.ts +1 -0
- package/index.js +30 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 1.0.2 (2025-08-13)
|
|
2
|
+
|
|
3
|
+
This was a version bump only for content-pane to align it with other projects, there were no code changes.
|
|
4
|
+
|
|
1
5
|
## 1.0.1 (2025-08-11)
|
|
2
6
|
|
|
3
7
|
This was a version bump only for content-pane to align it with other projects, there were no code changes.
|
package/README.md
CHANGED
|
@@ -1,215 +1,83 @@
|
|
|
1
|
-
# content-pane
|
|
1
|
+
# tj-content-pane
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Transforms plain html to a tree structure of sections, articles, and other elements. js-content-pane is a pure
|
|
4
|
+
Client-Side Rendering (CSR) solution. It is designed to style the unstyled output of static site generators (SSG) like
|
|
5
|
+
Jekyll, Hugo, or others. These provide SEO-friendly HTML output, but the structure is often not ideal for styling.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+

|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Applies layout transformations based on a compact layout attribute syntax.
|
|
10
|
-
- A small set of utilities you can call programmatically:
|
|
11
|
-
- SectionTreeBuilder: wraps related content into nested <section> containers.
|
|
12
|
-
- applyLayout: transforms elements based on a layout string (tag#id.class1.class2).
|
|
13
|
-
- attrAssign: helper to assign attributes to selected children.
|
|
9
|
+
Most Static Site Generators (SSG) support Kramdown, where you can assign attributes to elements in the markdown source by
|
|
10
|
+
using the `{: layout="selector" slot="slotname"}` syntax.
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
## Basic Usage
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
Wrap the area that should be transformed with the Custom Element:
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
```html
|
|
17
|
+
<tj-content-pane>
|
|
18
|
+
<h1>Header 1</h1>
|
|
19
|
+
<p>This is content below the header element.</p>
|
|
20
|
+
</tj-content-pane>
|
|
21
|
+
```
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
Normally this will happen in the template of a static site generator, like this:
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
```html
|
|
26
|
+
<tj-content-pane> {{content}} </tj-content-pane>
|
|
27
|
+
```
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
- @trunkjs/content-pane
|
|
29
|
+
## Layouts
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- Functions:
|
|
34
|
-
- applyLayout(elementOrElements, { recursive = true })
|
|
35
|
-
- attrAssign(element, multiQuerySelector, attributes)
|
|
36
|
-
- SectionTreeBuilder class
|
|
31
|
+
The Attribute `layout` can be used to specify a layout for the element. Use the css selector syntax to specify
|
|
32
|
+
tag, id or classes.
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
```markdown
|
|
35
|
+
## Header 2
|
|
36
|
+
{: layout="#id1.class1"}
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
This is content below the section element.
|
|
39
|
+
```
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
Will be transformed to:
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
2
|
|
49
|
-
|
|
43
|
+
```html
|
|
44
|
+
<section class="class1" id="id1">
|
|
45
|
+
<h2>Header 2</h2>
|
|
46
|
+
<p>This is content below the section element.</p>
|
|
47
|
+
</section>
|
|
48
|
+
```
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
### Subelements
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
The strcutrure of the content is defined by the h2-h6 elements.
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
```markdown
|
|
55
|
+
## Header 2
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
- Ensure the module is imported so the element is defined
|
|
59
|
-
- Add headings and content as children
|
|
57
|
+
text
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
<!-- somewhere in your app bootstrap -->
|
|
63
|
-
<script type="module">
|
|
64
|
-
import '@trunkjs/content-pane'; // registers <content-area2>
|
|
65
|
-
</script>
|
|
59
|
+
### Header 3
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
<h2 layout="2">Introduction</h2>
|
|
69
|
-
<p>This is the intro paragraph.</p>
|
|
61
|
+
text
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
<p>Some detailed text.</p>
|
|
63
|
+
### Header 3
|
|
73
64
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<p>Additional information appended at the same level as previous H2.</p>
|
|
65
|
+
text
|
|
66
|
+
```
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
<div layout="2.5;aside#toc.toc right">
|
|
80
|
-
<p>Table of contents here…</p>
|
|
81
|
-
</div>
|
|
82
|
-
</content-area2>
|
|
68
|
+
will transform to: (By default, the h2 elements are transformed to section elements, and h3-h6 elements to divs)
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- Variant:
|
|
100
|
-
- + → append: place the node inside the existing container at the computed level
|
|
101
|
-
- - → skip: do not start a new section for this node; it’s appended to the current container
|
|
102
|
-
- (none) → new: create a new section container at this level
|
|
103
|
-
- Level number:
|
|
104
|
-
- Optional number (e.g., 2, 2.5) scaled to 10s internally (2 → 20). If absent, heading tags provide the default.
|
|
105
|
-
- Decimals allow interleaving content between heading levels (e.g., 2.5 sits between H2 (20) and H3 (30)).
|
|
106
|
-
|
|
107
|
-
When creating a new <section>, SectionTreeBuilder:
|
|
108
|
-
- Moves attributes beginning with layout from the original node onto the new section and removes them from the original.
|
|
109
|
-
- Copies attributes starting with section- and converts section-* class names to classes on the section wrapper (prefix removed).
|
|
110
|
-
- For HR elements, copies all attributes to the section wrapper.
|
|
111
|
-
|
|
112
|
-
Note: The attribute handling is intentionally conservative to avoid disrupting your original elements more than necessary.
|
|
113
|
-
|
|
114
|
-
### Programmatic usage
|
|
115
|
-
|
|
116
|
-
import { SectionTreeBuilder } from '@trunkjs/content-pane';
|
|
117
|
-
|
|
118
|
-
const container = document.querySelector('#my-content') as HTMLElement;
|
|
119
|
-
const stb = new SectionTreeBuilder(container);
|
|
120
|
-
stb.arrange(Array.from(container.children));
|
|
121
|
-
|
|
122
|
-
This will restructure the container’s children into nested <section> elements.
|
|
123
|
-
|
|
124
|
-
## applyLayout
|
|
125
|
-
|
|
126
|
-
applyLayout transforms elements based on a compact layout attribute:
|
|
127
|
-
- Syntax: [prefix][;]selector
|
|
128
|
-
- Prefix: the same prefix as used by SectionTreeBuilder, typically used for sectioning (see above). applyLayout strips this prefix before applying the selector.
|
|
129
|
-
- Selector: a simplified CSS-like “element definition” of the form tag#id.class1.class2. If tag is omitted, defaults to div.
|
|
130
|
-
|
|
131
|
-
Behavior:
|
|
132
|
-
- For each element with a layout attribute, applyLayout:
|
|
133
|
-
- Parses the layout string, ignoring any leading sectioning prefix
|
|
134
|
-
- Creates a replacement element using the parsed tag, id, and classes
|
|
135
|
-
- Moves the original element’s children into the replacement element
|
|
136
|
-
- Replaces the original element in the DOM
|
|
137
|
-
- If the selector’s tag is a custom element name (contains -) and that element is not registered, applyLayout replaces it with an error element (TjErrorElement) to prevent infinite recursion and to make the issue visible in the DOM.
|
|
138
|
-
|
|
139
|
-
Options:
|
|
140
|
-
- recursive (default: true): apply the transformation to descendants as well.
|
|
141
|
-
|
|
142
|
-
Return value:
|
|
143
|
-
- An array of HTMLElements that were processed or produced by replacements.
|
|
144
|
-
|
|
145
|
-
Example:
|
|
146
|
-
import { applyLayout } from '@trunkjs/content-pane';
|
|
147
|
-
|
|
148
|
-
const el = document.querySelector('#article')!;
|
|
149
|
-
applyLayout(el, { recursive: true });
|
|
150
|
-
|
|
151
|
-
<!-- Before -->
|
|
152
|
-
<div id="sidebar" layout="2;aside#right.aside-panel">
|
|
153
|
-
<p>Sidebar content</p>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
<!-- After (conceptually) -->
|
|
157
|
-
<aside id="right" class="aside-panel" layout="2;aside#right.aside-panel">
|
|
158
|
-
<p>Sidebar content</p>
|
|
159
|
-
</aside>
|
|
160
|
-
|
|
161
|
-
Note: applyLayout preserves the original layout attribute on the replacement element so you can re-run the layout step if needed or inspect the intent.
|
|
162
|
-
|
|
163
|
-
### Manual before-layout hook
|
|
164
|
-
|
|
165
|
-
If a custom element used as a replacement implements a beforeLayoutCallback(origElement, instance, children) method, it will be called before children are attached and layout is applied to descendants. Returning false from this method will skip recursive layout for that element, allowing it to manage its own internal layout.
|
|
166
|
-
|
|
167
|
-
Type signature:
|
|
168
|
-
interface ManualBeforeLayoutElement {
|
|
169
|
-
beforeLayoutCallback(origElement: HTMLElement, instance: this, children: Element[]): void | boolean;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
## attrAssign
|
|
173
|
-
|
|
174
|
-
Utility to assign attributes to multiple selected descendants using a simple multi-selector separated by |.
|
|
175
|
-
|
|
176
|
-
Signature:
|
|
177
|
-
attrAssign(element: HTMLElement, multiQuerySelector: string, attributes: Record<string, string>): HTMLElement[]
|
|
178
|
-
|
|
179
|
-
- multiQuerySelector example: ':scope > .aside | :scope > *:has(img)'
|
|
180
|
-
- Returns an array of HTMLElements that received the attributes.
|
|
181
|
-
|
|
182
|
-
Example:
|
|
183
|
-
import { attrAssign } from '@trunkjs/content-pane';
|
|
184
|
-
|
|
185
|
-
const host = document.querySelector('section')!;
|
|
186
|
-
attrAssign(host, ':scope > img | :scope > figure', { slot: 'media' });
|
|
187
|
-
|
|
188
|
-
## Styling the generated structure
|
|
189
|
-
|
|
190
|
-
Because <content-area2> does not use Shadow DOM, you can style:
|
|
191
|
-
- section wrappers produced by SectionTreeBuilder
|
|
192
|
-
- any elements produced by applyLayout
|
|
193
|
-
- your original content elements
|
|
194
|
-
|
|
195
|
-
Typical patterns:
|
|
196
|
-
- Target sections based on heading-derived structure
|
|
197
|
-
- Use IDs and classes assigned via layout or attrAssign
|
|
198
|
-
- Combine with CSS container queries for responsive layouts
|
|
199
|
-
|
|
200
|
-
## Notes and caveats
|
|
201
|
-
|
|
202
|
-
- H1 is treated as H2 (i = 20) to keep top-level content consistent.
|
|
203
|
-
- HR becomes a divider at half-steps (+5) between major heading levels.
|
|
204
|
-
- The package assumes you will either:
|
|
205
|
-
- Use <content-area2>, which orchestrates both steps, or
|
|
206
|
-
- Manually call SectionTreeBuilder then applyLayout in that order.
|
|
207
|
-
- If you reference an unregistered custom element in a layout selector, an error element is inserted to make the problem visible and to avoid infinite recursion.
|
|
208
|
-
|
|
209
|
-
## Building
|
|
210
|
-
|
|
211
|
-
Run `nx build content-pane` to build the library.
|
|
212
|
-
|
|
213
|
-
## Running unit tests
|
|
214
|
-
|
|
215
|
-
Run `nx test content-pane` to execute the unit tests via [Vitest](https://vitest.dev/).
|
|
70
|
+
```html
|
|
71
|
+
<section>
|
|
72
|
+
<h2>Header 2</h2>
|
|
73
|
+
<p>text</p>
|
|
74
|
+
<div>
|
|
75
|
+
<h3>Header 3</h3>
|
|
76
|
+
<p>text</p>
|
|
77
|
+
</div>
|
|
78
|
+
<div>
|
|
79
|
+
<h3>Header 3</h3>
|
|
80
|
+
<p>text</p>
|
|
81
|
+
</div>
|
|
82
|
+
</section>
|
|
83
|
+
```
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
var C = Object.defineProperty;
|
|
2
|
-
var
|
|
3
|
-
var h = (n, t, r) =>
|
|
4
|
-
import { unsafeCSS as
|
|
5
|
-
import { property as
|
|
6
|
-
import { create_element as
|
|
2
|
+
var _ = (n, t, r) => t in n ? C(n, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : n[t] = r;
|
|
3
|
+
var h = (n, t, r) => _(n, typeof t != "symbol" ? t + "" : t, r);
|
|
4
|
+
import { unsafeCSS as E, LitElement as A, html as N, ReactiveElement as I } from "lit";
|
|
5
|
+
import { property as L, customElement as y } from "lit/decorators.js";
|
|
6
|
+
import { create_element as b, LoggingMixin as k, Stopwatch as T, waitForDomContentLoaded as P } from "@trunkjs/browser-utils";
|
|
7
|
+
function V(n) {
|
|
8
|
+
return n && typeof n == "object" && "__I__" in n && typeof n.__I__ == "object" && "i" in n.__I__;
|
|
9
|
+
}
|
|
7
10
|
class S {
|
|
8
11
|
constructor(t, r = !1) {
|
|
9
12
|
h(this, "rootNode");
|
|
@@ -35,7 +38,7 @@ class S {
|
|
|
35
38
|
return o;
|
|
36
39
|
}
|
|
37
40
|
createNewContainerNode(t, r) {
|
|
38
|
-
const o = this.getAttributeRecords(t, t.tagName === "HR"), e =
|
|
41
|
+
const o = this.getAttributeRecords(t, t.tagName === "HR"), e = b("section", o);
|
|
39
42
|
return e.__IT = r, e;
|
|
40
43
|
}
|
|
41
44
|
arrangeSingleNode(t, r) {
|
|
@@ -69,12 +72,12 @@ class S {
|
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
const $ = ":host{--border-color: red;--background-color: lightgray;font-family:Arial,sans-serif}#error-fixed-indicator{position:fixed;top:10px;right:10px;cursor:pointer;z-index:100000;padding:5px 10px;width:auto;max-width:90vw;min-width:100px;height:auto;box-shadow:0 4px 8px #0003;border:5px solid white;color:#fff;background-color:red;animation:blink 1s infinite;border-radius:15px;font-size:20px;font-weight:700;font-family:Arial,sans-serif}@keyframes blink{0%,to{background-color:#000}50%{background-color:red}}#error{background-color:var(--background-color);border:3px solid var(--border-color);padding:10px;margin:10px;border-radius:5px}h1{color:red;font-size:24px;margin:0}.error-details{font-size:14px;max-height:200px;overflow:auto}";
|
|
72
|
-
var
|
|
73
|
-
for (var e = o > 1 ? void 0 : o ?
|
|
75
|
+
var w = Object.defineProperty, M = Object.getOwnPropertyDescriptor, O = (n, t, r) => t in n ? w(n, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : n[t] = r, v = (n, t, r, o) => {
|
|
76
|
+
for (var e = o > 1 ? void 0 : o ? M(t, r) : t, s = n.length - 1, i; s >= 0; s--)
|
|
74
77
|
(i = n[s]) && (e = (o ? i(t, r, e) : i(e)) || e);
|
|
75
|
-
return o && e &&
|
|
76
|
-
},
|
|
77
|
-
let f = class extends
|
|
78
|
+
return o && e && w(t, r, e), e;
|
|
79
|
+
}, R = (n, t, r) => O(n, t + "", r);
|
|
80
|
+
let f = class extends A {
|
|
78
81
|
constructor(t = "An error occurred", r) {
|
|
79
82
|
super();
|
|
80
83
|
h(this, "originalCode");
|
|
@@ -85,7 +88,7 @@ let f = class extends N {
|
|
|
85
88
|
return "tj-error-element";
|
|
86
89
|
}
|
|
87
90
|
render() {
|
|
88
|
-
return
|
|
91
|
+
return N`
|
|
89
92
|
<div id="error-fixed-indicator" @click=${() => this.scrollIntoView({ behavior: "smooth" })}>
|
|
90
93
|
Err: ${this.message}
|
|
91
94
|
</div>
|
|
@@ -101,14 +104,14 @@ let f = class extends N {
|
|
|
101
104
|
`;
|
|
102
105
|
}
|
|
103
106
|
};
|
|
104
|
-
|
|
107
|
+
R(f, "styles", [E($)]);
|
|
105
108
|
v([
|
|
106
|
-
|
|
109
|
+
L({ type: String, reflect: !0 })
|
|
107
110
|
], f.prototype, "message", 2);
|
|
108
111
|
f = v([
|
|
109
|
-
|
|
112
|
+
y("tj-error-element")
|
|
110
113
|
], f);
|
|
111
|
-
function
|
|
114
|
+
function j(n, { allowAttributes: t = !0, ignoreGaps: r = !0 } = {}) {
|
|
112
115
|
let o = "div", e = null, s = [], i = [], l = {};
|
|
113
116
|
const d = /(^[a-z][\w-]*)|#[\w-]+|\.[\w:-]+|\[\s*([\w-]+)(?:\s*=\s*(['"]?)(.*?)\3)?\s*\]/gi;
|
|
114
117
|
let a = 0;
|
|
@@ -137,10 +140,10 @@ function D(n) {
|
|
|
137
140
|
function F(n, t, r) {
|
|
138
141
|
var u, c;
|
|
139
142
|
console.log("Applying layout to element:", n, "with layout:", r);
|
|
140
|
-
const o = /^(\+|-|)([0-9]+\.?[0-9]*);?/, e = r.replace(o, ""), s =
|
|
143
|
+
const o = /^(\+|-|)([0-9]+\.?[0-9]*);?/, e = r.replace(o, ""), s = j(e), i = { class: "" };
|
|
141
144
|
s.attrsMap.class && (i.class = s.attrsMap.class + " "), i.class += s.classes.join(" "), i.id = s.id, ((u = i.class) == null ? void 0 : u.trim()) === "" && delete i.class, ((c = i.id) == null ? void 0 : c.trim()) === "" && delete i.id;
|
|
142
145
|
const l = s.tag || "div";
|
|
143
|
-
let d = !1, a =
|
|
146
|
+
let d = !1, a = b(l, { ...i, layoutOrig: r });
|
|
144
147
|
if (l.includes("-") && !customElements.get(l))
|
|
145
148
|
console.warn(`Custom element <${l}> is not registered.`), a = new f(`Custom element <${l}> is not registered.`, n.outerHTML), n.replaceWith(a), a.append(n), d = !0;
|
|
146
149
|
else {
|
|
@@ -180,7 +183,7 @@ var W = Object.getOwnPropertyDescriptor, z = (n, t, r, o) => {
|
|
|
180
183
|
(i = n[s]) && (e = i(e) || e);
|
|
181
184
|
return e;
|
|
182
185
|
};
|
|
183
|
-
let
|
|
186
|
+
let x = class extends k(I) {
|
|
184
187
|
static get is() {
|
|
185
188
|
return "tj-content-pane";
|
|
186
189
|
}
|
|
@@ -197,10 +200,10 @@ let b = class extends I(L) {
|
|
|
197
200
|
t.arrange(r), g(Array.from(this.children), { recursive: !0 }), n.lap("after arrange");
|
|
198
201
|
}
|
|
199
202
|
};
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
],
|
|
203
|
-
function
|
|
203
|
+
x = z([
|
|
204
|
+
y("tj-content-pane")
|
|
205
|
+
], x);
|
|
206
|
+
function J(n, t, r) {
|
|
204
207
|
const o = [], e = t.split("|");
|
|
205
208
|
for (const s of e) {
|
|
206
209
|
const i = n.querySelectorAll(s.trim());
|
|
@@ -214,7 +217,9 @@ function V(n, t, r) {
|
|
|
214
217
|
return o;
|
|
215
218
|
}
|
|
216
219
|
export {
|
|
217
|
-
|
|
220
|
+
x as ContentAreaElement2,
|
|
221
|
+
S as SectionTreeBuilder,
|
|
218
222
|
g as applyLayout,
|
|
219
|
-
|
|
223
|
+
J as attrAssign,
|
|
224
|
+
V as isSectionTreeElement
|
|
220
225
|
};
|