@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 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
- This library was generated with [Nx](https://nx.dev).
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
- A lightweight toolkit for turning a flat block of HTML content into a, styled “content pane.” It provides:
7
+ ![Demo Markdown](docs/tj-content-pane-title1.png)
6
8
 
7
- - A Web Component (<content-area2>) that automatically:
8
- - Builds a nested section structure from your headings and horizontal rules.
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
- Status: experimental / evolving API.
12
+ ## Basic Usage
16
13
 
17
- ## Why use content-pane?
14
+ Wrap the area that should be transformed with the Custom Element:
18
15
 
19
- If you have document-like content (headings, paragraphs, images, etc.) and want:
20
- - Automatic, semantic grouping of content into sections
21
- - Declarative, inline layout instructions per element
22
- - A no-shadow-DOM approach that keeps your stylesheets simple
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
- …content-pane is for you.
23
+ Normally this will happen in the template of a static site generator, like this:
25
24
 
26
- ## Packages and exports
25
+ ```html
26
+ <tj-content-pane> {{content}} </tj-content-pane>
27
+ ```
27
28
 
28
- Install or import from:
29
- - @trunkjs/content-pane
29
+ ## Layouts
30
30
 
31
- Exports:
32
- - Web Component: registered as custom element content-area2
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
- Import examples:
39
- - Register the custom element (side-effect import): import '@trunkjs/content-pane'
40
- - Use utilities: import { applyLayout, attrAssign } from '@trunkjs/content-pane'
34
+ ```markdown
35
+ ## Header 2
36
+ {: layout="#id1.class1"}
41
37
 
42
- ## The <content-area2> Web Component
38
+ This is content below the section element.
39
+ ```
43
40
 
44
- The component is defined without Shadow DOM (createRenderRoot returns this), so your global CSS can style the generated structure directly.
41
+ Will be transformed to:
45
42
 
46
- What it does on connect:
47
- 1. Waits for DOMContentLoaded
48
- 2. Builds a section tree from its direct children using SectionTreeBuilder
49
- 3. Applies layout transformations to itself and its descendants using applyLayout
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
- Tag name: content-area2
50
+ ### Subelements
52
51
 
53
- Note: The internal class is ContentAreaElement2. The static is getter returns 'tj-content-area' but the actual registered tag is content-area2.
52
+ The strcutrure of the content is defined by the h2-h6 elements.
54
53
 
55
- ### Quick start (browser)
54
+ ```markdown
55
+ ## Header 2
56
56
 
57
- - Include content-area2 on your page
58
- - Ensure the module is imported so the element is defined
59
- - Add headings and content as children
57
+ text
60
58
 
61
- Example:
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
- <content-area2>
68
- <h2 layout="2">Introduction</h2>
69
- <p>This is the intro paragraph.</p>
61
+ text
70
62
 
71
- <h3>Details</h3>
72
- <p>Some detailed text.</p>
63
+ ### Header 3
73
64
 
74
- <hr> <!-- becomes a mid-level divider -->
75
- <h2 layout="+2">More</h2>
76
- <p>Additional information appended at the same level as previous H2.</p>
65
+ text
66
+ ```
77
67
 
78
- <!-- An element with layout that transforms into an <aside> -->
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
- ## The SectionTreeBuilder
85
-
86
- SectionTreeBuilder rearranges a flat list of nodes into nested <section> elements based on:
87
- - Heading levels (H1–H6) mapped to a 10s scale (H2 → 20, H3 → 30, …). H1 is treated as H2.
88
- - Horizontal rules (HR) which become implicit dividers at lastFixedI + 5.
89
- - Optional layout prefix directives on elements to influence grouping.
90
-
91
- It processes only element nodes (other node types are appended as-is) and respects a small “index” (i) model for nesting.
92
-
93
- ### Layout prefix for sectioning
94
-
95
- If a node has a layout attribute, the prefix can shape the sectioning behavior:
96
-
97
- Pattern: ^(\+|-|)([0-9]?\.?[0-9]?|)(;|$)
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
@@ -1,3 +1,4 @@
1
1
  export * from './components/tj-content-pane/TjContentPane';
2
2
  export * from './lib/apply-layout';
3
3
  export * from './lib/attrAssign';
4
+ export * from './lib/SectionTreeBuilder';
package/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  var C = Object.defineProperty;
2
- var E = (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) => E(n, typeof t != "symbol" ? t + "" : t, r);
4
- import { unsafeCSS as A, LitElement as N, html as _, ReactiveElement as L } from "lit";
5
- import { property as k, customElement as x } from "lit/decorators.js";
6
- import { create_element as w, LoggingMixin as I, Stopwatch as T, waitForDomContentLoaded as P } from "@trunkjs/browser-utils";
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 = w("section", o);
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 y = Object.defineProperty, j = Object.getOwnPropertyDescriptor, M = (n, t, r) => t in n ? y(n, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : n[t] = r, v = (n, t, r, o) => {
73
- for (var e = o > 1 ? void 0 : o ? j(t, r) : t, s = n.length - 1, i; s >= 0; s--)
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 && y(t, r, e), e;
76
- }, O = (n, t, r) => M(n, t + "", r);
77
- let f = class extends N {
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
- O(f, "styles", [A($)]);
107
+ R(f, "styles", [E($)]);
105
108
  v([
106
- k({ type: String, reflect: !0 })
109
+ L({ type: String, reflect: !0 })
107
110
  ], f.prototype, "message", 2);
108
111
  f = v([
109
- x("tj-error-element")
112
+ y("tj-error-element")
110
113
  ], f);
111
- function R(n, { allowAttributes: t = !0, ignoreGaps: r = !0 } = {}) {
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 = R(e), i = { class: "" };
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 = w(l, { ...i, layoutOrig: r });
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 b = class extends I(L) {
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
- b = z([
201
- x("tj-content-pane")
202
- ], b);
203
- function V(n, t, r) {
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
- b as ContentAreaElement2,
220
+ x as ContentAreaElement2,
221
+ S as SectionTreeBuilder,
218
222
  g as applyLayout,
219
- V as attrAssign
223
+ J as attrAssign,
224
+ V as isSectionTreeElement
220
225
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trunkjs/content-pane",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "main": "./index.js",
5
5
  "dependencies": {
6
6
  "@trunkjs/browser-utils": "^1.0.12",