mancha 0.11.0 → 0.12.0
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/.github/workflows/ci.yml +41 -0
- package/README.md +20 -6
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +3 -2
- package/dist/css_gen_utils.js +37 -27
- package/dist/dome.d.ts +3 -0
- package/dist/dome.js +33 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/interfaces.d.ts +1 -1
- package/dist/mancha.js +2 -2
- package/dist/plugins.js +106 -77
- package/dist/{core.d.ts → renderer.d.ts} +1 -0
- package/dist/safe_browser.d.ts +2 -1
- package/dist/safe_browser.js +45 -8
- package/dist/store.d.ts +1 -1
- package/dist/store.js +1 -1
- package/dist/test_utils.d.ts +10 -0
- package/dist/test_utils.js +44 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +2 -1
- package/gulpfile.js +7 -4
- package/package.json +20 -9
- package/tsconfig.json +1 -0
- /package/dist/{core.js → renderer.js} +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: "pages"
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build-test-deploy:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
name: Build Test Deploy
|
|
21
|
+
environment:
|
|
22
|
+
name: github-pages
|
|
23
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: "22"
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: npm install --include=dev
|
|
31
|
+
- name: Build project
|
|
32
|
+
run: npm run build
|
|
33
|
+
- name: Test project
|
|
34
|
+
run: npm run test
|
|
35
|
+
- name: Upload artifact
|
|
36
|
+
uses: actions/upload-pages-artifact@v3
|
|
37
|
+
with:
|
|
38
|
+
path: "dist"
|
|
39
|
+
- name: Deploy to GitHub Pages
|
|
40
|
+
id: deployment
|
|
41
|
+
uses: actions/deploy-pages@v4
|
package/README.md
CHANGED
|
@@ -63,7 +63,8 @@ front-end development. Whether you decide to break up your app into reusable par
|
|
|
63
63
|
`<include>` or create custom web components, you can write HTML as if your mother was watching.
|
|
64
64
|
|
|
65
65
|
`mancha` implements its own reactivity engine, so the bundled browser module contains no external
|
|
66
|
-
dependencies
|
|
66
|
+
dependencies with the exception of [`jexpr`][jexpr] for safe expression evaluation (see the
|
|
67
|
+
[dependencies section](#dependencies)).
|
|
67
68
|
|
|
68
69
|
## Preprocessing
|
|
69
70
|
|
|
@@ -161,7 +162,7 @@ element tag or attributes match a specific criteria. Here's the list of attribut
|
|
|
161
162
|
|
|
162
163
|
To avoid violation of Content Security Policy (CSP) that forbids the use of `eval()`, `Mancha`
|
|
163
164
|
evaluates all expressions using [`jexpr`][jexpr]. This means that only simple expressions are
|
|
164
|
-
allowed, and spaces must be used to separate different
|
|
165
|
+
allowed, and spaces must be used to separate different expression tokens. For example:
|
|
165
166
|
|
|
166
167
|
```html
|
|
167
168
|
<!-- Valid expression: string concatenation -->
|
|
@@ -218,13 +219,13 @@ allowed, and spaces must be used to separate different expressions tokens. For e
|
|
|
218
219
|
</body>
|
|
219
220
|
```
|
|
220
221
|
|
|
221
|
-
## Scoping
|
|
222
|
+
## Variable Scoping
|
|
222
223
|
|
|
223
224
|
Contents of the `:data` attribute are only available to subnodes in the HTML tree. This is better
|
|
224
225
|
illustrated with an example:
|
|
225
226
|
|
|
226
227
|
```html
|
|
227
|
-
<body :data="{ name: 'stranger' }">
|
|
228
|
+
<body :data="{ name: 'stranger', key: '1234' }">
|
|
228
229
|
<!-- Hello, stranger -->
|
|
229
230
|
<h1>Hello, {{ name }}</h1>
|
|
230
231
|
|
|
@@ -233,7 +234,7 @@ illustrated with an example:
|
|
|
233
234
|
|
|
234
235
|
<!-- How are you, danger? The secret message is "secret" -->
|
|
235
236
|
<p :data="{ name: 'danger', message: 'secret' }">
|
|
236
|
-
How are you, {{ name }}? The secret message is: "{{ message }}".
|
|
237
|
+
How are you, {{ name }}? The secret message is: "{{ message }}"".
|
|
237
238
|
</p>
|
|
238
239
|
</body>
|
|
239
240
|
```
|
|
@@ -265,6 +266,19 @@ const subrenderer = document.querySelector("p").renderer;
|
|
|
265
266
|
subrenderer.$.message = "banana";
|
|
266
267
|
```
|
|
267
268
|
|
|
269
|
+
To access variables defined in the parent renderer, you can use the subrenderer's `$parent`
|
|
270
|
+
attribute:
|
|
271
|
+
|
|
272
|
+
```html
|
|
273
|
+
<body :data="{ name: 'stranger' }">
|
|
274
|
+
<!-- Hello, stranger! -->
|
|
275
|
+
<p :data="{}">Hello, {{ $parent.name }}!</p>
|
|
276
|
+
</body>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Renderers also have a `$root` attribute, which references the root element where `mancha` was
|
|
280
|
+
mounted and defaults to the document's body, unless explicitly provided.
|
|
281
|
+
|
|
268
282
|
## Styling
|
|
269
283
|
|
|
270
284
|
Some basic styling rules are built into the library and can be optionally used. The styling
|
|
@@ -272,7 +286,7 @@ component was designed to be used in the browser, and it's enabled by adding a `
|
|
|
272
286
|
to the `<script>` tag that loads `mancha`. The supported rulesets are:
|
|
273
287
|
|
|
274
288
|
- `basic`: inspired by [these rules](https://www.swyx.io/css-100-bytes), the full CSS can be found
|
|
275
|
-
[here](./src/
|
|
289
|
+
[here](./src/css_gen_basic.ts).
|
|
276
290
|
- `utils`: utility classes inspired by [tailwindcss](https://tailwindcss.com), the resulting CSS is
|
|
277
291
|
a drop-in replacement for a subset of the classes provided by `tailwindcss` with the main
|
|
278
292
|
exception of the color palette which is borrowed from
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { IRenderer } from "./
|
|
1
|
+
import { IRenderer } from "./renderer.js";
|
|
2
2
|
import { ParserParams, RenderParams } from "./interfaces.js";
|
|
3
3
|
export declare class Renderer extends IRenderer {
|
|
4
|
+
readonly impl = "browser";
|
|
4
5
|
protected readonly dirpath: string;
|
|
5
6
|
parseHTML(content: string, params?: ParserParams): Document | DocumentFragment;
|
|
6
7
|
serializeHTML(root: Node | DocumentFragment): string;
|
package/dist/browser.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { IRenderer } from "./
|
|
1
|
+
import { IRenderer } from "./renderer.js";
|
|
2
2
|
import { dirname } from "./dome.js";
|
|
3
3
|
export class Renderer extends IRenderer {
|
|
4
|
-
|
|
4
|
+
impl = "browser";
|
|
5
|
+
dirpath = dirname(globalThis.location?.href ?? "http://localhost/");
|
|
5
6
|
parseHTML(content, params = { rootDocument: false }) {
|
|
6
7
|
if (params.rootDocument) {
|
|
7
8
|
return new DOMParser().parseFromString(content, "text/html");
|
package/dist/css_gen_utils.js
CHANGED
|
@@ -461,7 +461,7 @@ function posneg(props) {
|
|
|
461
461
|
// Positive percent units.
|
|
462
462
|
...PERCENTS.map((v) => [`${klass}-${v}\\%`, `${prop}: ${v}%`]),
|
|
463
463
|
// Negative percent units.
|
|
464
|
-
...PERCENTS.map((v) => [`-${klass}-${v}\\%`,
|
|
464
|
+
...PERCENTS.map((v) => [`-${klass}-${v}\\%`, `${prop}: -${v}%`]),
|
|
465
465
|
])
|
|
466
466
|
.flatMap(([klass, rule]) => [
|
|
467
467
|
`.${klass} { ${rule} }`,
|
|
@@ -470,29 +470,35 @@ function posneg(props) {
|
|
|
470
470
|
]);
|
|
471
471
|
}
|
|
472
472
|
function autoxy(props) {
|
|
473
|
-
return Object.entries(props)
|
|
473
|
+
return Object.entries(props)
|
|
474
|
+
.flatMap(([prop, klass]) => [
|
|
474
475
|
// Auto.
|
|
475
|
-
|
|
476
|
+
[`${klass}-auto`, `${prop}: auto`],
|
|
476
477
|
// Auto x-axis.
|
|
477
|
-
|
|
478
|
+
[`${klass}x-auto`, `${prop}-left: auto; ${prop}-right: auto;`],
|
|
478
479
|
// Auto y-axis.
|
|
479
|
-
|
|
480
|
+
[`${klass}y-auto`, `${prop}-top: auto; ${prop}-bottom: auto;`],
|
|
480
481
|
// Zero x-axis.
|
|
481
|
-
|
|
482
|
+
[`${klass}x-0`, `${prop}-left: 0; ${prop}-right: 0;`],
|
|
482
483
|
// Zero y-axis.
|
|
483
|
-
|
|
484
|
+
[`${klass}y-0`, `${prop}-top: 0; ${prop}-bottom: 0;`],
|
|
484
485
|
// Positive REM units x-axis.
|
|
485
|
-
...UNITS_ALL.map((v) => [v, v * REM_UNIT]).map(([k, v]) =>
|
|
486
|
+
...UNITS_ALL.map((v) => [v, v * REM_UNIT]).map(([k, v]) => [`${klass}x-${k}`, `${prop}-left: ${v}rem; ${prop}-right: ${v}rem;`]),
|
|
486
487
|
// Positive REM units y-axis.
|
|
487
|
-
...UNITS_ALL.map((v) => [v, v * REM_UNIT]).map(([k, v]) =>
|
|
488
|
+
...UNITS_ALL.map((v) => [v, v * REM_UNIT]).map(([k, v]) => [`${klass}y-${k}`, `${prop}-top: ${v}rem; ${prop}-bottom: ${v}rem;`]),
|
|
488
489
|
// Positive PX units x-axis.
|
|
489
|
-
...UNITS_ALL.map((v) =>
|
|
490
|
+
...UNITS_ALL.map((v) => [`${klass}x-${v}px`, `${prop}-left: ${v}px; ${prop}-right: ${v}px;`]),
|
|
490
491
|
// Positive PX units y-axis.
|
|
491
|
-
...UNITS_ALL.map((v) =>
|
|
492
|
+
...UNITS_ALL.map((v) => [`${klass}y-${v}px`, `${prop}-top: ${v}px; ${prop}-bottom: ${v}px;`]),
|
|
492
493
|
// Positive percent units x-axis.
|
|
493
|
-
...PERCENTS.map((v) =>
|
|
494
|
+
...PERCENTS.map((v) => [`${klass}x-${v}\\%`, `${prop}-left: ${v}%; ${prop}-right: ${v}%;`]),
|
|
494
495
|
// Positive percent units y-axis.
|
|
495
|
-
...PERCENTS.map((v) =>
|
|
496
|
+
...PERCENTS.map((v) => [`${klass}y-${v}\\%`, `${prop}-top: ${v}%; ${prop}-bottom: ${v}%;`]),
|
|
497
|
+
])
|
|
498
|
+
.flatMap(([klass, rule]) => [
|
|
499
|
+
`.${klass} { ${rule} }`,
|
|
500
|
+
`${wrapPseudoStates(klass).join(",")} { ${rule} }`,
|
|
501
|
+
...wrapMediaQueries(klass, rule),
|
|
496
502
|
]);
|
|
497
503
|
}
|
|
498
504
|
function tblr(props) {
|
|
@@ -573,32 +579,36 @@ function border() {
|
|
|
573
579
|
function between() {
|
|
574
580
|
return [
|
|
575
581
|
// Zero for x margin.
|
|
576
|
-
|
|
582
|
+
[`space-x-0 > *`, `margin-left: 0`],
|
|
577
583
|
// Zero for y margin.
|
|
578
|
-
|
|
584
|
+
[`space-y-0 > *`, `margin-top: 0`],
|
|
579
585
|
// Positive REM units for x margin.
|
|
580
|
-
...UNITS_ALL.map((v) =>
|
|
586
|
+
...UNITS_ALL.map((v) => [`space-x-${v} > :not(:first-child)`, `margin-left: ${v * REM_UNIT}rem`]),
|
|
581
587
|
// Positive REM units for y margin.
|
|
582
|
-
...UNITS_ALL.map((v) =>
|
|
588
|
+
...UNITS_ALL.map((v) => [`space-y-${v} > :not(:first-child)`, `margin-top: ${v * REM_UNIT}rem`]),
|
|
583
589
|
// Positive PX units for x margin.
|
|
584
|
-
...UNITS_ALL.map((v) =>
|
|
590
|
+
...UNITS_ALL.map((v) => [`space-x-${v}px > :not(:first-child)`, `margin-left: ${v}px`]),
|
|
585
591
|
// Positive PX units for y margin.
|
|
586
|
-
...UNITS_ALL.map((v) =>
|
|
592
|
+
...UNITS_ALL.map((v) => [`space-y-${v}px > :not(:first-child)`, `margin-top: ${v}px`]),
|
|
587
593
|
// Zero for gap.
|
|
588
|
-
|
|
594
|
+
[`gap-0`, `gap: 0`],
|
|
589
595
|
// Positive REM units for gap.
|
|
590
|
-
...UNITS_ALL.map((v) =>
|
|
596
|
+
...UNITS_ALL.map((v) => [`gap-${v}`, `gap: ${v * REM_UNIT}rem`]),
|
|
591
597
|
// Positive PX units for gap.
|
|
592
|
-
...UNITS_ALL.map((v) =>
|
|
598
|
+
...UNITS_ALL.map((v) => [`gap-${v}px`, `gap: ${v}px`]),
|
|
593
599
|
// Positive REM units for col gap.
|
|
594
|
-
...UNITS_ALL.map((v) =>
|
|
600
|
+
...UNITS_ALL.map((v) => [`gap-x-${v}`, `column-gap: ${v * REM_UNIT}rem`]),
|
|
595
601
|
// Positive REM units for row gap.
|
|
596
|
-
...UNITS_ALL.map((v) =>
|
|
602
|
+
...UNITS_ALL.map((v) => [`gap-y-${v}`, `row-gap: ${v * REM_UNIT}rem`]),
|
|
597
603
|
// Positive PX units for col gap.
|
|
598
|
-
...UNITS_ALL.map((v) =>
|
|
604
|
+
...UNITS_ALL.map((v) => [`gap-x-${v}px`, `column-gap: ${v}px`]),
|
|
599
605
|
// Positive PX units for row gap.
|
|
600
|
-
...UNITS_ALL.map((v) =>
|
|
601
|
-
]
|
|
606
|
+
...UNITS_ALL.map((v) => [`gap-y-${v}px`, `row-gap: ${v}px`]),
|
|
607
|
+
].flatMap(([klass, rule]) => [
|
|
608
|
+
`.${klass} { ${rule} }`,
|
|
609
|
+
`${wrapPseudoStates(klass).join(",")} { ${rule} }`,
|
|
610
|
+
...wrapMediaQueries(klass, rule),
|
|
611
|
+
]);
|
|
602
612
|
}
|
|
603
613
|
function custom() {
|
|
604
614
|
return Object.entries(PROPS_CUSTOM).flatMap(([klass, props]) => Object.entries(props).flatMap(([propkey, propval]) => [
|
package/dist/dome.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type ElementWithAttribs = Element & {
|
|
2
|
+
dataset?: DOMStringMap;
|
|
2
3
|
attribs?: {
|
|
3
4
|
[key: string]: string;
|
|
4
5
|
};
|
|
@@ -21,10 +22,12 @@ export declare function hasFunction(obj: any, func: string): boolean;
|
|
|
21
22
|
*/
|
|
22
23
|
export declare function attributeNameToCamelCase(name: string): string;
|
|
23
24
|
export declare function getAttribute(elem: ElementWithAttribs, name: string): string | null;
|
|
25
|
+
export declare function getAttributeOrDataset(elem: ElementWithAttribs, name: string, attributePrefix?: string): string | null;
|
|
24
26
|
export declare function setAttribute(elem: ElementWithAttribs, name: string, value: string): void;
|
|
25
27
|
export declare function safeSetAttribute(elem: ElementWithAttribs, name: string, value: string): void;
|
|
26
28
|
export declare function setProperty(elem: ElementWithAttribs, name: string, value: any): void;
|
|
27
29
|
export declare function removeAttribute(elem: ElementWithAttribs, name: string): void;
|
|
30
|
+
export declare function removeAttributeOrDataset(elem: ElementWithAttribs, name: string, prefix?: string): void;
|
|
28
31
|
export declare function cloneAttribute(elemFrom: ElementWithAttribs, elemDest: ElementWithAttribs, name: string): void;
|
|
29
32
|
export declare function firstElementChild(elem: Element): Element | null;
|
|
30
33
|
export declare function replaceWith(original: ChildNode, ...replacement: Node[]): void;
|
package/dist/dome.js
CHANGED
|
@@ -45,7 +45,11 @@ export function getAttribute(elem, name) {
|
|
|
45
45
|
if (hasProperty(elem, "attribs"))
|
|
46
46
|
return elem.attribs?.[name] ?? null;
|
|
47
47
|
else
|
|
48
|
-
return elem.getAttribute?.(name);
|
|
48
|
+
return elem.getAttribute?.(name) ?? null;
|
|
49
|
+
}
|
|
50
|
+
export function getAttributeOrDataset(elem, name, attributePrefix = "") {
|
|
51
|
+
return (getAttribute(elem, attributePrefix + name) ||
|
|
52
|
+
(elem.dataset?.[attributeNameToCamelCase(name)] ?? null));
|
|
49
53
|
}
|
|
50
54
|
export function setAttribute(elem, name, value) {
|
|
51
55
|
if (hasProperty(elem, "attribs"))
|
|
@@ -60,7 +64,21 @@ export function safeSetAttribute(elem, name, value) {
|
|
|
60
64
|
safeElement.setPrefixedAttribute(SAFE_ATTRS, elem, name, value);
|
|
61
65
|
}
|
|
62
66
|
export function setProperty(elem, name, value) {
|
|
63
|
-
|
|
67
|
+
switch (name) {
|
|
68
|
+
// Directly set some safe, known properties.
|
|
69
|
+
case "disabled":
|
|
70
|
+
elem.disabled = value;
|
|
71
|
+
return;
|
|
72
|
+
case "selected":
|
|
73
|
+
elem.selected = value;
|
|
74
|
+
return;
|
|
75
|
+
case "checked":
|
|
76
|
+
elem.checked = value;
|
|
77
|
+
return;
|
|
78
|
+
// Fall back to setting the property directly (unsafe).
|
|
79
|
+
default:
|
|
80
|
+
elem[name] = value;
|
|
81
|
+
}
|
|
64
82
|
}
|
|
65
83
|
export function removeAttribute(elem, name) {
|
|
66
84
|
if (hasProperty(elem, "attribs"))
|
|
@@ -68,10 +86,18 @@ export function removeAttribute(elem, name) {
|
|
|
68
86
|
else
|
|
69
87
|
elem.removeAttribute?.(name);
|
|
70
88
|
}
|
|
89
|
+
export function removeAttributeOrDataset(elem, name, prefix = "") {
|
|
90
|
+
removeAttribute(elem, `${prefix}${name}`);
|
|
91
|
+
removeAttribute(elem, `data-${name}`);
|
|
92
|
+
}
|
|
71
93
|
export function cloneAttribute(elemFrom, elemDest, name) {
|
|
72
94
|
if (hasProperty(elemFrom, "attribs") && hasProperty(elemDest, "attribs")) {
|
|
73
95
|
elemDest.attribs[name] = elemFrom.attribs[name];
|
|
74
96
|
}
|
|
97
|
+
else if (name.startsWith("data-")) {
|
|
98
|
+
const datasetKey = attributeNameToCamelCase(name.slice(5));
|
|
99
|
+
elemDest.dataset[datasetKey] = elemFrom.dataset?.[datasetKey];
|
|
100
|
+
}
|
|
75
101
|
else {
|
|
76
102
|
const attr = elemFrom?.getAttribute?.(name);
|
|
77
103
|
safeSetAttribute(elemDest, name, attr || "");
|
|
@@ -150,6 +176,11 @@ export function ellipsize(str, maxLength = 0) {
|
|
|
150
176
|
return str.slice(0, maxLength - 1) + "…";
|
|
151
177
|
}
|
|
152
178
|
export function nodeToString(node, maxLength = 0) {
|
|
179
|
+
if (globalThis.DocumentFragment && node instanceof DocumentFragment) {
|
|
180
|
+
return Array.from(node.childNodes)
|
|
181
|
+
.map((node) => nodeToString(node, maxLength))
|
|
182
|
+
.join("");
|
|
183
|
+
}
|
|
153
184
|
return ellipsize(node.outerHTML || node.nodeValue || String(node), maxLength);
|
|
154
185
|
}
|
|
155
186
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ParserParams, RenderParams } from "./interfaces.js";
|
|
2
|
-
import { IRenderer } from "./
|
|
2
|
+
import { IRenderer } from "./renderer.js";
|
|
3
3
|
export declare class Renderer extends IRenderer {
|
|
4
|
+
readonly impl = "jsdom";
|
|
4
5
|
parseHTML(content: string, params?: ParserParams): Document | DocumentFragment;
|
|
5
6
|
serializeHTML(root: Node | DocumentFragment | Document): string;
|
|
6
7
|
createElement(tag: string, owner?: Document | null): Element;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import { JSDOM } from "jsdom";
|
|
3
|
-
import { IRenderer } from "./
|
|
3
|
+
import { IRenderer } from "./renderer.js";
|
|
4
4
|
export class Renderer extends IRenderer {
|
|
5
|
+
impl = "jsdom";
|
|
5
6
|
parseHTML(content, params = { rootDocument: false }) {
|
|
6
7
|
if (params.rootDocument) {
|
|
7
8
|
return new JSDOM(content).window.document;
|
package/dist/interfaces.d.ts
CHANGED