mancha 0.9.0 → 0.9.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,8 +1,7 @@
1
1
  # mancha
2
2
 
3
3
  `mancha` is a simple HTML templating and reactivity library for simple people. It works on the
4
- browser or the server. It can be used as a command-line tool, imported as a Javascript module, or as
5
- a plugin for [`Gulp`](https://gulpjs.com).
4
+ browser or the server. It can be used as a command-line tool, or imported as a Javascript module.
6
5
 
7
6
  Here's a small sample of the things that you can do with `mancha`:
8
7
 
@@ -16,7 +15,7 @@ Here's a small sample of the things that you can do with `mancha`:
16
15
  <template is="counter">
17
16
  <div>
18
17
  <slot></slot>
19
- <button @click="count++">Counter: {{ count }}</button>
18
+ <button :on:click="count = count + 1">Counter: {{ count }}</button>
20
19
  </div>
21
20
  </template>
22
21
 
@@ -48,7 +47,7 @@ None of them have all the key features that make `mancha` unique:
48
47
  | Feature | mancha | Svelte | React.js | Vue.js | petite-vue | Alpine.js |
49
48
  | --------------------- | ------ | ------ | -------- | ------ | ---------- | --------- |
50
49
  | Simple to learn | ✔️ | ❌ | ❌ | ❌ | ✔️ | ✔️ |
51
- | < 12kb compressed | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ |
50
+ | < 15kb compressed | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ |
52
51
  | Custom web components | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
53
52
  | Client-side rendering | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ✔️ |
54
53
  | Server-side rendering | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
@@ -99,7 +98,7 @@ preprocessing consist of:
99
98
  </template>
100
99
 
101
100
  <!-- Any node traversed after registration can use the component. -->
102
- <my-red-button @click="console.log('clicked')">
101
+ <my-red-button :on:click="console.log('clicked')">
103
102
  <!-- The contents within will replace the `<slot></slot>` tag. -->
104
103
  Click Me
105
104
  </my-red-button>
@@ -119,43 +118,31 @@ element tag or attributes match a specific criteria. Here's the list of attribut
119
118
  ```html
120
119
  <div :for="item in ['a', 'b', 'c']">{{ item }}</div>
121
120
  ```
122
- - `$text` sets the `textContent` value of a node
121
+ - `:text` sets the `textContent` value of a node
123
122
  ```html
124
- <div :data="{foo: 'bar'}" $text="foo"></div>
123
+ <div :data="{foo: 'bar'}" :text="foo"></div>
125
124
  ```
126
- - `$html` sets the `innerHTML` value of a node
125
+ - `:html` sets the `innerHTML` value of a node
127
126
  ```html
128
- <div $html="<span>Hello World</span>"></div>
127
+ <div :html="<span>Hello World</span>"></div>
129
128
  ```
130
129
  - `:show` toggles `$elem.style.display` to `none`
131
130
  ```html
132
131
  <div :data="{foo: false}" :show="foo"></div>
133
132
  ```
134
- - `@watch` executes an expression anytime its dependencies change
135
- ```html
136
- <div :data="{foo: 'bar'}" @watch="console.log('foo changed:', foo)"></div>
137
- ```
138
133
  - `:bind` binds (two-way) a variable to the `value` or `checked` property of the element.
139
134
  ```html
140
135
  <div :data="{name: 'Stranger'}">
141
136
  <input type="text" :bind="name" />
142
137
  </div>
143
138
  ```
144
- - `${prop}` binds (one-way) the node property `prop` with the given expression
145
- ```html
146
- <div :data="{foo: false}">
147
- <input type="submit" $disabled="foo" />
148
- </div>
149
- ```
150
- - `:{attr}` binds (one-way) the node attribute `attr` with the given expression
139
+ - `:on:{event}` adds an event listener for `event` to the node
151
140
  ```html
152
- <div :data="{foo: 'bar'}">
153
- <input type="text" :placeholder="foo" />
154
- </div>
141
+ <button :on:click="console.log('clicked')"></button>
155
142
  ```
156
- - `@{event}` adds an event listener for `event` to the node
143
+ - `:{attribute}` sets the corresponding property for `attribute` in the node
157
144
  ```html
158
- <button @click="console.log('clicked')"></button>
145
+ <a :href="buildUrl()"></a>
159
146
  ```
160
147
  - `{{ value }}` replaces `value` in text nodes
161
148
  ```html
@@ -278,28 +265,13 @@ self.addEventListener("fetch", async (event) => {
278
265
 
279
266
  To meet the size requirements of popular worker runtimes, the worker version of `mancha` uses
280
267
  `htmlparser2` instead of `jsdom` for the underlying HTML and DOM manipulation. This keeps the
281
- footprint of `mancha` under 100kb.
268
+ footprint of `mancha` and its dependencies under 100kb.
282
269
 
283
270
  For a more complete example, see [examples/wrangler](./examples/wrangler).
284
271
 
285
- ## Compile Time `Gulp` Plugin
272
+ ## Dependencies
286
273
 
287
- To use `mancha` as a `Gulp` plugin in `gulpfile.js`, you can do the following:
274
+ The browser bundle contains a single external dependency, [`jexpr`][jexpr]. The unbundled version
275
+ can use `htmlparser2`, which is compatible with web workers, or `jsdom`.
288
276
 
289
- ```js
290
- import GulpClient from "gulp";
291
- import { mancha } from "mancha/dist/gulp";
292
- import vars from "./vars.json";
293
-
294
- GulpClient.task("build", function () {
295
- return (
296
- GulpClient
297
- // Inlcude all HTML files, but exclude all partials (ending in .tpl.html).
298
- .src(["src/**/*.html", "!src/**/*.tpl/html"])
299
- // Render the HTML content using `mancha`.
300
- .pipe(mancha(vars))
301
- // Pipe the output to the destination folder.
302
- .pipe(GulpClient.dest("public"))
303
- );
304
- });
305
- ```
277
+ [jexpr]: https://github.com/justinfagnani/jexpr
package/dist/browser.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { IRenderer } from "./core.js";
2
2
  import { ParserParams, RenderParams } from "./interfaces.js";
3
- declare class Renderer extends IRenderer {
3
+ export declare class Renderer extends IRenderer {
4
4
  protected readonly dirpath: string;
5
- parseHTML(content: string, params?: ParserParams): DocumentFragment;
5
+ parseHTML(content: string, params?: ParserParams): Document | DocumentFragment;
6
6
  serializeHTML(root: Node | DocumentFragment): string;
7
- preprocessLocal(fpath: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
7
+ preprocessLocal(fpath: string, params?: RenderParams & ParserParams): Promise<Document | DocumentFragment>;
8
+ createElement(tag: string, owner?: Document | null): Element;
9
+ textContent(node: Node, content: string): void;
8
10
  }
9
- declare const Mancha: Renderer;
10
- export default Mancha;
11
+ export declare const Mancha: Renderer;
package/dist/browser.js CHANGED
@@ -1,7 +1,6 @@
1
- import { dirname, IRenderer } from "./core.js";
2
- import basicCssRules from "./css_gen_basic.js";
3
- import utilsCssRules from "./css_gen_utils.js";
4
- class Renderer extends IRenderer {
1
+ import { IRenderer } from "./core.js";
2
+ import { dirname } from "./dome.js";
3
+ export class Renderer extends IRenderer {
5
4
  dirpath = dirname(self.location.href);
6
5
  parseHTML(content, params = { rootDocument: false }) {
7
6
  if (params.rootDocument) {
@@ -20,39 +19,11 @@ class Renderer extends IRenderer {
20
19
  // In the browser, "local" paths (i.e., relative paths) can still be fetched.
21
20
  return this.preprocessRemote(fpath, params);
22
21
  }
23
- }
24
- const Mancha = new Renderer();
25
- self["Mancha"] = Mancha;
26
- const currentScript = self.document?.currentScript;
27
- // If the init attribute is present, mount the content to the target element(s).
28
- if (self.document?.currentScript?.hasAttribute("init")) {
29
- const debug = currentScript?.hasAttribute("debug");
30
- const cachePolicy = currentScript?.getAttribute("cache");
31
- const targets = currentScript?.getAttribute("target")?.split("+") || ["body"];
32
- window.addEventListener("load", () => {
33
- targets.map(async (target) => {
34
- const fragment = self.document.querySelector(target);
35
- await Mancha.debug(debug).mount(fragment, { cache: cachePolicy });
36
- });
37
- });
38
- }
39
- // If the css attribute is present, inject the specified CSS rules.
40
- if (self.document?.currentScript?.hasAttribute("css")) {
41
- const styleNames = currentScript?.getAttribute("css")?.split("+");
42
- for (const styleName of styleNames) {
43
- const style = document.createElement("style");
44
- switch (styleName) {
45
- case "basic":
46
- style.textContent = basicCssRules();
47
- break;
48
- case "utils":
49
- style.textContent = utilsCssRules();
50
- break;
51
- default:
52
- console.error(`Unknown style name: "${styleName}"`);
53
- break;
54
- }
55
- self.document.head.appendChild(style);
22
+ createElement(tag, owner) {
23
+ return (owner || document).createElement(tag);
24
+ }
25
+ textContent(node, content) {
26
+ node.textContent = content;
56
27
  }
57
28
  }
58
- export default Mancha;
29
+ export const Mancha = new Renderer();
package/dist/cli.d.ts CHANGED
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
package/dist/cli.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import * as fs from "fs/promises";
3
2
  import yargs from "yargs";
4
3
  import { hideBin } from "yargs/helpers";
package/dist/core.d.ts CHANGED
@@ -1,26 +1,6 @@
1
1
  import { ParserParams, RenderParams } from "./interfaces.js";
2
2
  import { SignalStore } from "./store.js";
3
3
  export type EvalListener = (result: any, dependencies: string[]) => any;
4
- /**
5
- * Returns the directory name from a given file path.
6
- * @param fpath - The file path.
7
- * @returns The directory name.
8
- */
9
- export declare function dirname(fpath: string): string;
10
- /**
11
- * Checks if a given file path is a relative path.
12
- *
13
- * @param fpath - The file path to check.
14
- * @returns A boolean indicating whether the file path is relative or not.
15
- */
16
- export declare function isRelativePath(fpath: string): boolean;
17
- /**
18
- * Creates an evaluation function based on the provided code and arguments.
19
- * @param code The code to be evaluated.
20
- * @param args The arguments to be passed to the evaluation function. Default is an empty array.
21
- * @returns The evaluation function.
22
- */
23
- export declare function makeEvalFunction(code: string, args?: string[]): Function;
24
4
  /**
25
5
  * Represents an abstract class for rendering and manipulating HTML content.
26
6
  * Extends the `ReactiveProxyStore` class.
@@ -30,8 +10,10 @@ export declare abstract class IRenderer extends SignalStore {
30
10
  protected readonly dirpath: string;
31
11
  readonly _skipNodes: Set<Node>;
32
12
  readonly _customElements: Map<string, Node>;
33
- abstract parseHTML(content: string, params?: ParserParams): DocumentFragment;
13
+ abstract parseHTML(content: string, params?: ParserParams): Document | DocumentFragment;
34
14
  abstract serializeHTML(root: DocumentFragment | Node): string;
15
+ abstract createElement(tag: string, owner?: Document | null): Element;
16
+ abstract textContent(node: Node, tag: string): void;
35
17
  /**
36
18
  * Sets the debugging flag for the current instance.
37
19
  *
@@ -61,21 +43,21 @@ export declare abstract class IRenderer extends SignalStore {
61
43
  * @param params - Optional rendering and parsing parameters.
62
44
  * @returns A promise that resolves to a DocumentFragment representing the preprocessed content.
63
45
  */
64
- preprocessString(content: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
46
+ preprocessString(content: string, params?: RenderParams & ParserParams): Promise<Document | DocumentFragment>;
65
47
  /**
66
48
  * Preprocesses a remote file by fetching its content and applying preprocessing steps.
67
49
  * @param fpath - The path to the remote file.
68
50
  * @param params - Optional parameters for rendering and parsing.
69
51
  * @returns A Promise that resolves to a DocumentFragment representing the preprocessed content.
70
52
  */
71
- preprocessRemote(fpath: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
53
+ preprocessRemote(fpath: string, params?: RenderParams & ParserParams): Promise<Document | DocumentFragment>;
72
54
  /**
73
55
  * Preprocesses a local file by fetching its content and applying preprocessing steps.
74
56
  * @param fpath - The path to the local file.
75
57
  * @param params - Optional parameters for rendering and parsing.
76
58
  * @returns A promise that resolves to the preprocessed document fragment.
77
59
  */
78
- preprocessLocal(fpath: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
60
+ preprocessLocal(fpath: string, params?: RenderParams & ParserParams): Promise<Document | DocumentFragment>;
79
61
  /**
80
62
  * Creates a deep copy of the current renderer instance.
81
63
  * @returns A new instance of the renderer with the same state as the original.
package/dist/core.js CHANGED
@@ -1,41 +1,7 @@
1
1
  import { Iterator } from "./iterator.js";
2
2
  import { RendererPlugins } from "./plugins.js";
3
- import { nodeToString, traverse } from "./dome.js";
3
+ import { dirname, nodeToString, traverse } from "./dome.js";
4
4
  import { SignalStore } from "./store.js";
5
- /**
6
- * Returns the directory name from a given file path.
7
- * @param fpath - The file path.
8
- * @returns The directory name.
9
- */
10
- export function dirname(fpath) {
11
- if (!fpath.includes("/")) {
12
- return "";
13
- }
14
- else {
15
- return fpath.split("/").slice(0, -1).join("/");
16
- }
17
- }
18
- /**
19
- * Checks if a given file path is a relative path.
20
- *
21
- * @param fpath - The file path to check.
22
- * @returns A boolean indicating whether the file path is relative or not.
23
- */
24
- export function isRelativePath(fpath) {
25
- return (!fpath.includes("://") &&
26
- !fpath.startsWith("/") &&
27
- !fpath.startsWith("#") &&
28
- !fpath.startsWith("data:"));
29
- }
30
- /**
31
- * Creates an evaluation function based on the provided code and arguments.
32
- * @param code The code to be evaluated.
33
- * @param args The arguments to be passed to the evaluation function. Default is an empty array.
34
- * @returns The evaluation function.
35
- */
36
- export function makeEvalFunction(code, args = []) {
37
- return new Function(...args, `with (this) { return (async () => (${code}))(); }`);
38
- }
39
5
  /**
40
6
  * Represents an abstract class for rendering and manipulating HTML content.
41
7
  * Extends the `ReactiveProxyStore` class.
@@ -183,24 +149,22 @@ export class IRenderer extends SignalStore {
183
149
  await RendererPlugins.resolveDataAttribute.call(this, node, params);
184
150
  // Resolve the :for attribute in the node.
185
151
  await RendererPlugins.resolveForAttribute.call(this, node, params);
186
- // Resolve the $text attribute in the node.
152
+ // Resolve the :text attribute in the node.
187
153
  await RendererPlugins.resolveTextAttributes.call(this, node, params);
188
- // Resolve the $html attribute in the node.
154
+ // Resolve the :html attribute in the node.
189
155
  await RendererPlugins.resolveHtmlAttribute.call(this, node, params);
190
156
  // Resolve the :show attribute in the node.
191
157
  await RendererPlugins.resolveShowAttribute.call(this, node, params);
192
- // Resolve the @watch attribute in the node.
193
- await RendererPlugins.resolveWatchAttribute.call(this, node, params);
158
+ // Resolve the :class attribute in the node.
159
+ await RendererPlugins.resolveClassAttribute.call(this, node, params);
194
160
  // Resolve the :bind attribute in the node.
195
161
  await RendererPlugins.resolveBindAttribute.call(this, node, params);
196
- // Resolve all $attributes in the node.
197
- await RendererPlugins.resolvePropAttributes.call(this, node, params);
198
- // Resolve all :attributes in the node.
199
- await RendererPlugins.resolveAttrAttributes.call(this, node, params);
200
- // Resolve all @attributes in the node.
162
+ // Resolve all :on:event attributes in the node.
201
163
  await RendererPlugins.resolveEventAttributes.call(this, node, params);
202
164
  // Replace all the {{ variables }} in the text.
203
165
  await RendererPlugins.resolveTextNodeExpressions.call(this, node, params);
166
+ // Resolve the :{attr} attribute in the node.
167
+ await RendererPlugins.resolveCustomAttribute.call(this, node, params);
204
168
  }
205
169
  // Return the input node, which should now be fully rendered.
206
170
  return root;
@@ -1 +1,2 @@
1
- export default function rules(): string;
1
+ import { SafeStyleSheet } from "safevalues";
2
+ export default function rules(): SafeStyleSheet;
@@ -1,4 +1,22 @@
1
- import basic from "./css_raw_basic.css";
1
+ import { safeStyleSheet } from "safevalues";
2
2
  export default function rules() {
3
- return basic;
3
+ // Based on https://www.swyx.io/css-100-bytes.
4
+ return safeStyleSheet `
5
+ html {
6
+ max-width: 70ch;
7
+ padding: 2em 1em;
8
+ margin: auto;
9
+ line-height: 1.75;
10
+ font-size: 1.25em;
11
+ font-family: sans-serif;
12
+ }
13
+
14
+ h1,h2,h3,h4,h5,h6 {
15
+ margin: 1em 0 0.5em;
16
+ }
17
+
18
+ p,ul,ol {
19
+ margin-bottom: 1em;
20
+ color: #1d1d1d;
21
+ }`;
4
22
  }
package/dist/dome.d.ts CHANGED
@@ -1,8 +1,3 @@
1
- import { ChildNode as _ChildNode, Element as _Element, Node as _Node, ParentNode as _ParentNode } from "domhandler";
2
- type __Node = Node | _Node;
3
- type __ParentNode = ParentNode | _ParentNode;
4
- type __Element = Element | _Element;
5
- type __ChildNode = ChildNode | _ChildNode;
6
1
  /**
7
2
  * Traverses the DOM tree starting from the given root node and yields each child node.
8
3
  * Nodes in the `skip` set will be skipped during traversal.
@@ -12,29 +7,36 @@ type __ChildNode = ChildNode | _ChildNode;
12
7
  * @returns A generator that yields each child node in the DOM tree.
13
8
  */
14
9
  export declare function traverse(root: Node | DocumentFragment | Document, skip?: Set<Node>): Generator<ChildNode>;
10
+ export declare function hasProperty(obj: any, prop: string): boolean;
11
+ export declare function hasFunction(obj: any, func: string): boolean;
15
12
  /**
16
13
  * Converts from an attribute name to camelCase, e.g. `foo-bar` becomes `fooBar`.
17
14
  * @param name attribute name
18
15
  * @returns camel-cased attribute name
19
16
  */
20
17
  export declare function attributeNameToCamelCase(name: string): string;
21
- export declare function getAttribute(elem: __Element, name: string): string | null;
22
- export declare function setAttribute(elem: __Element, name: string, value: string): void;
23
- export declare function removeAttribute(elem: __Element, name: string): void;
24
- export declare function cloneAttribute(elemFrom: __Element, elemDest: __Element, name: string): void;
25
- export declare function firstElementChild(elem: __Element): __Element | null;
26
- export declare function replaceWith(original: __ChildNode, ...replacement: __Node[]): void;
27
- export declare function appendChild(parent: __Node, node: __Node): __Node;
28
- export declare function removeChild(parent: __Node, node: __Node): __Node;
29
- export declare function replaceChildren(parent: __ParentNode, ...nodes: __Node[]): void;
30
- export declare function insertBefore(parent: __Node, node: __Node, reference: __ChildNode | null): __Node;
31
- export declare function innerHTML(elem: Element | _Element): string;
32
- export declare function innerText(elem: Element | _Element): string | null;
33
- export declare function getTextContent(elem: Element | _Element): string | null;
34
- export declare function setTextContent(elem: Element | _Element, value: string): void;
35
- export declare function getNodeValue(node: Node | _Node): string | null;
36
- export declare function setNodeValue(node: Node | _Node, value: string | null): void;
37
- export declare function createElement(tagName: string, document: Document | null): Element | _Element;
18
+ export declare function getAttribute(elem: Element | any, name: string): string | null;
19
+ export declare function setAttribute(elem: Element | any, name: string, value: string): void;
20
+ export declare function removeAttribute(elem: Element | any, name: string): void;
21
+ export declare function cloneAttribute(elemFrom: Element | any, elemDest: Element | any, name: string): void;
22
+ export declare function firstElementChild(elem: Element): Element | null;
23
+ export declare function replaceWith(original: ChildNode, ...replacement: Node[]): void;
24
+ export declare function replaceChildren(parent: ParentNode, ...nodes: Node[]): void;
25
+ export declare function appendChild(parent: Node, node: Node): Node;
26
+ export declare function removeChild(parent: ParentNode, node: Node): Node;
27
+ export declare function insertBefore(parent: Node, node: Node, reference: ChildNode | null): Node;
38
28
  export declare function ellipsize(str: string | null, maxLength?: number): string;
39
- export declare function nodeToString(node: Node | _Node, maxLength?: number): string;
40
- export {};
29
+ export declare function nodeToString(node: Node, maxLength?: number): string;
30
+ /**
31
+ * Returns the directory name from a given file path.
32
+ * @param fpath - The file path.
33
+ * @returns The directory name.
34
+ */
35
+ export declare function dirname(fpath: string): string;
36
+ /**
37
+ * Checks if a given file path is a relative path.
38
+ *
39
+ * @param fpath - The file path to check.
40
+ * @returns A boolean indicating whether the file path is relative or not.
41
+ */
42
+ export declare function isRelativePath(fpath: string): boolean;
package/dist/dome.js CHANGED
@@ -1,5 +1,10 @@
1
- import { Element as _Element, Node as _Node, Text as _Text, } from "domhandler";
2
- import { DomUtils } from "htmlparser2";
1
+ import { safeAttrPrefix } from "safevalues";
2
+ import { safeElement } from "safevalues/dom";
3
+ const SAFE_ATTRS = [
4
+ safeAttrPrefix `:`,
5
+ safeAttrPrefix `style`,
6
+ safeAttrPrefix `class`,
7
+ ];
3
8
  /**
4
9
  * Traverses the DOM tree starting from the given root node and yields each child node.
5
10
  * Nodes in the `skip` set will be skipped during traversal.
@@ -26,7 +31,10 @@ export function* traverse(root, skip = new Set()) {
26
31
  }
27
32
  }
28
33
  }
29
- function hasFunction(obj, func) {
34
+ export function hasProperty(obj, prop) {
35
+ return typeof obj?.[prop] !== "undefined";
36
+ }
37
+ export function hasFunction(obj, func) {
30
38
  return typeof obj?.[func] === "function";
31
39
  }
32
40
  /**
@@ -38,38 +46,39 @@ export function attributeNameToCamelCase(name) {
38
46
  return name.replace(/-./g, (c) => c[1].toUpperCase());
39
47
  }
40
48
  export function getAttribute(elem, name) {
41
- if (elem instanceof _Element)
42
- return elem.attribs?.[name];
49
+ if (hasProperty(elem, "attribs"))
50
+ return elem.attribs?.[name] ?? null;
43
51
  else
44
52
  return elem.getAttribute?.(name);
45
53
  }
46
54
  export function setAttribute(elem, name, value) {
47
- if (elem instanceof _Element)
55
+ if (hasProperty(elem, "attribs"))
48
56
  elem.attribs[name] = value;
49
57
  else
50
- elem.setAttribute?.(name, value);
58
+ safeElement.setPrefixedAttribute(SAFE_ATTRS, elem, name, value);
51
59
  }
52
60
  export function removeAttribute(elem, name) {
53
- if (elem instanceof _Element)
61
+ if (hasProperty(elem, "attribs"))
54
62
  delete elem.attribs[name];
55
63
  else
56
64
  elem.removeAttribute?.(name);
57
65
  }
58
66
  export function cloneAttribute(elemFrom, elemDest, name) {
59
- if (elemFrom instanceof _Element && elemDest instanceof _Element) {
67
+ if (hasProperty(elemFrom, "attribs") && hasProperty(elemDest, "attribs")) {
60
68
  elemDest.attribs[name] = elemFrom.attribs[name];
61
69
  }
62
70
  else {
63
- const attr = elemFrom?.getAttributeNode?.(name);
64
- elemDest?.setAttributeNode?.(attr?.cloneNode(true));
71
+ const attr = elemFrom?.getAttribute?.(name);
72
+ setAttribute(elemDest, name, attr || "");
65
73
  }
66
74
  }
67
75
  export function firstElementChild(elem) {
68
- if (elem instanceof _Element) {
69
- return elem.children.find((child) => child instanceof _Element);
76
+ if (hasProperty(elem, "firstElementChild")) {
77
+ return elem.firstElementChild;
70
78
  }
71
79
  else {
72
- return elem.firstElementChild;
80
+ const children = Array.from(elem.children);
81
+ return children.find((child) => child.nodeType === 1);
73
82
  }
74
83
  }
75
84
  export function replaceWith(original, ...replacement) {
@@ -87,6 +96,15 @@ export function replaceWith(original, ...replacement) {
87
96
  .concat(Array.from(parent.childNodes).slice(index + 1));
88
97
  }
89
98
  }
99
+ export function replaceChildren(parent, ...nodes) {
100
+ if (hasFunction(parent, "replaceChildren")) {
101
+ parent.replaceChildren(...nodes);
102
+ }
103
+ else {
104
+ parent.childNodes = nodes;
105
+ nodes.forEach((node) => (node.parentNode = parent));
106
+ }
107
+ }
90
108
  export function appendChild(parent, node) {
91
109
  if (hasFunction(node, "appendChild")) {
92
110
  return parent.appendChild(node);
@@ -102,18 +120,8 @@ export function removeChild(parent, node) {
102
120
  return parent.removeChild(node);
103
121
  }
104
122
  else {
105
- const elem = node;
106
- parent.childNodes = parent.children.filter((child) => child !== elem);
107
- return elem;
108
- }
109
- }
110
- export function replaceChildren(parent, ...nodes) {
111
- if (hasFunction(parent, "replaceChildren")) {
112
- parent.replaceChildren(...nodes);
113
- }
114
- else {
115
- parent.childNodes = nodes;
116
- nodes.forEach((node) => (node.parentNode = parent));
123
+ replaceChildren(parent, ...Array.from(parent.childNodes).filter((child) => child !== node));
124
+ return node;
117
125
  }
118
126
  }
119
127
  export function insertBefore(parent, node, reference) {
@@ -128,48 +136,6 @@ export function insertBefore(parent, node, reference) {
128
136
  return node;
129
137
  }
130
138
  }
131
- export function innerHTML(elem) {
132
- if (elem instanceof _Element)
133
- return DomUtils.getInnerHTML(elem);
134
- else
135
- return elem.innerHTML;
136
- }
137
- export function innerText(elem) {
138
- if (elem instanceof _Element)
139
- return DomUtils.innerText(elem);
140
- else
141
- return elem.innerText;
142
- }
143
- export function getTextContent(elem) {
144
- if (elem instanceof _Element)
145
- return DomUtils.textContent(elem);
146
- else
147
- return elem.textContent;
148
- }
149
- export function setTextContent(elem, value) {
150
- if (elem instanceof _Element)
151
- elem.children = [new _Text(value)];
152
- else
153
- elem.textContent = value;
154
- }
155
- export function getNodeValue(node) {
156
- if (node instanceof _Node)
157
- return node.data;
158
- else
159
- return node.nodeValue;
160
- }
161
- export function setNodeValue(node, value) {
162
- if (node instanceof _Node)
163
- node.data = value;
164
- else
165
- node.nodeValue = value;
166
- }
167
- export function createElement(tagName, document) {
168
- if (document)
169
- return document.createElement(tagName);
170
- else
171
- return new _Element(tagName, {});
172
- }
173
139
  export function ellipsize(str, maxLength = 0) {
174
140
  if (!str)
175
141
  return "";
@@ -179,5 +145,30 @@ export function ellipsize(str, maxLength = 0) {
179
145
  return str.slice(0, maxLength - 1) + "…";
180
146
  }
181
147
  export function nodeToString(node, maxLength = 0) {
182
- return ellipsize(node.outerHTML || getNodeValue(node) || String(node), maxLength);
148
+ return ellipsize(node.outerHTML || node.nodeValue || String(node), maxLength);
149
+ }
150
+ /**
151
+ * Returns the directory name from a given file path.
152
+ * @param fpath - The file path.
153
+ * @returns The directory name.
154
+ */
155
+ export function dirname(fpath) {
156
+ if (!fpath.includes("/")) {
157
+ return "";
158
+ }
159
+ else {
160
+ return fpath.split("/").slice(0, -1).join("/");
161
+ }
162
+ }
163
+ /**
164
+ * Checks if a given file path is a relative path.
165
+ *
166
+ * @param fpath - The file path to check.
167
+ * @returns A boolean indicating whether the file path is relative or not.
168
+ */
169
+ export function isRelativePath(fpath) {
170
+ return (!fpath.includes("://") &&
171
+ !fpath.startsWith("/") &&
172
+ !fpath.startsWith("#") &&
173
+ !fpath.startsWith("data:"));
183
174
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { ParserParams, RenderParams } from "./interfaces.js";
2
2
  import { IRenderer } from "./core.js";
3
3
  export declare class Renderer extends IRenderer {
4
- parseHTML(content: string, params?: ParserParams): DocumentFragment;
4
+ parseHTML(content: string, params?: ParserParams): Document | DocumentFragment;
5
5
  serializeHTML(root: Node | DocumentFragment | Document): string;
6
+ createElement(tag: string, owner?: Document | null): Element;
7
+ textContent(node: Node, content: string): void;
6
8
  fetchLocal(fpath: string, params?: RenderParams & ParserParams): Promise<string>;
7
9
  }
8
10
  export declare const Mancha: Renderer;