@schukai/monster 5.0.0 → 5.1.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/CHANGELOG.md CHANGED
@@ -2,9 +2,34 @@
2
2
 
3
3
 
4
4
 
5
+ ## [5.1.0] - 2026-10-11
6
+
7
+ ### Add Features
8
+
9
+ - **style:** add composable content surfaces ([#598](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/598))
10
+
11
+
12
+
13
+ ## [5.0.1] - 2026-10-11
14
+
15
+ ### Bug Fixes
16
+
17
+ - **docs:** ship Monster 5 migration guide ([#597](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/597))
18
+
19
+
20
+
5
21
  ## [5.0.0] - 2026-10-10
6
22
 
23
+ ### Breaking Changes
7
24
 
25
+ - **custom-element:** `getOption()`, `setOption()` and `setOptions()` now throw
26
+ `OptionsNotInitializedError` while `defaults` or `customization` are being
27
+ evaluated ([#596](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/596)).
28
+ Keep both getters and their helpers declarative. Use `initMethodSymbol` for
29
+ initialized constructor options and `assembleMethodSymbol` after late
30
+ attributes or script configuration have been applied. Monster 5 deliberately
31
+ provides no compatibility switch, early fallback or write queue. See the
32
+ [Monster 5 options migration guide](documentation/migrations/monster-5-options.md).
8
33
 
9
34
  ## [4.154.12] - 2026-10-10
10
35
 
package/README.md CHANGED
@@ -25,6 +25,14 @@ For certain functions, additional [polyfills](#polyfill) may be required.
25
25
 
26
26
  To access our documentation and examples, visit [monsterjs.org/](https://monsterjs.org/).
27
27
 
28
+ ### Migrating to Monster 5
29
+
30
+ Monster 5 makes the custom-element option lifecycle strict. Option access is
31
+ not available while `defaults` or `customization` are being evaluated. See the
32
+ [Monster 5 options migration guide](documentation/migrations/monster-5-options.md)
33
+ for the lifecycle contract, migration examples and the correct use of
34
+ `initMethodSymbol` and `assembleMethodSymbol`.
35
+
28
36
  ## Installation
29
37
 
30
38
  Install Monster using npm, Yarn, or pnpm:
@@ -0,0 +1,90 @@
1
+ # Monster 5: initialized options
2
+
3
+ `CustomElement.getOption()`, `setOption()` and `setOptions()` require an initialized
4
+ option observer. Calls made while the constructor evaluates `defaults` or
5
+ `customization` throw `OptionsNotInitializedError`. The error is exported from
6
+ `source/dom/customelement.mjs` and the main Monster entry point; its `method` and
7
+ `component` properties identify the attempted operation and class.
8
+
9
+ Monster 4.154.10 restored a fallback for early reads. Monster 5 removes that
10
+ fallback. There is no compatibility switch, dependency injection setting or
11
+ queue for early writes. Normal fallback values for missing initialized options
12
+ continue to work, as do `false`, `0`, `""` and `null`.
13
+
14
+ ## Lifecycle
15
+
16
+ 1. The constructor reads `defaults`, applies available `data-monster-option-*`
17
+ attributes and applies `customization` (evaluated once).
18
+ 2. It creates the option observer, then calls `initMethodSymbol`. All three
19
+ option methods are now usable. Derived instance fields are not initialized
20
+ yet: this hook runs inside the base constructor.
21
+ 3. On first connection, option attributes are read again. The base assembly
22
+ merges `data-monster-options`, then the script selected by
23
+ `data-monster-options-selector`, before rendering and connecting updaters.
24
+ 4. Runtime setters notify observers. `setOptions()` merges without resetting
25
+ defaults. Reconnection rebinds the existing DOM and updater snapshot; it does
26
+ not repeat assembly or reload the JSON/script configuration.
27
+
28
+ Available attributes depend on the creation path. In particular,
29
+ `document.createElement()` cannot see attributes the caller will set later.
30
+ An initialized API is therefore not a promise that all configuration sources
31
+ have already been applied. Existing attribute conversion and precedence remain
32
+ unchanged, including the attribute reread on connection.
33
+
34
+ ## Migration
35
+
36
+ This creates a circular dependency and is no longer supported:
37
+
38
+ ```js
39
+ get defaults() {
40
+ return { ...super.defaults, title: this.getOption("labels.title", "Details") };
41
+ }
42
+ ```
43
+
44
+ Define initial values without reading the options under construction:
45
+
46
+ ```js
47
+ get defaults() {
48
+ return {
49
+ ...super.defaults,
50
+ labels: { title: "Details" },
51
+ templates: {
52
+ main: '<h2 data-monster-replace="path:labels.title"></h2>',
53
+ },
54
+ };
55
+ }
56
+ ```
57
+
58
+ Updater-bound markup reads the assembled configuration and follows subsequent
59
+ changes. For imperative work that needs JSON/script options, use the existing
60
+ assembly hook after its `super` call:
61
+
62
+ ```js
63
+ [assembleMethodSymbol]() {
64
+ super[assembleMethodSymbol]();
65
+ this.configureFromResource(this.getOption("resourceConfig"));
66
+ return this;
67
+ }
68
+ ```
69
+
70
+ At that point the base template has already rendered. Do not move template
71
+ configuration after rendering and expect it to rebuild automatically. Prefer a
72
+ static template with updater bindings, or update the rendered component through
73
+ its established API. Do not permanently cache IDs or context keys from
74
+ constructor-time fallbacks.
75
+
76
+ Work needing only constructor configuration can use `initMethodSymbol` after
77
+ its `super` call. Callbacks stored in defaults may read options when invoked
78
+ later; the restriction concerns execution during initialization, not where a
79
+ callback is declared. The same rule applies to helpers called by either getter.
80
+
81
+ Consumer subclasses must migrate before adopting Monster 5. Other option
82
+ classes, including the deprecated `BaseWithOptions`, are outside this change.
83
+
84
+ ## Verification
85
+
86
+ Issue #596 adds API lifecycle tests, a local AST call-graph check, and browser
87
+ construction/assembly coverage for the registered component catalog. The static
88
+ check resolves direct calls, local helpers and methods; it is not a proof about
89
+ imported or dynamically selected functions. Browser coverage complements it.
90
+ Existing form, updater, datatable and reconnect suites remain release gates.
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","exports":{".":"./source/monster.mjs","./CHANGELOG.md":"./CHANGELOG.md","./LICENSE":"./LICENSE","./README.md":"./README.md","./package.json":"./package.json","./source/*":"./source/*","./test/*":"./test/*"},"files":["source","test/cases","test/util","CHANGELOG.md"],"homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"sideEffects":["./source/monster.mjs","./source/components/**","./source/i18n/**","./test/**"],"type":"module","version":"5.0.0"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","exports":{".":"./source/monster.mjs","./CHANGELOG.md":"./CHANGELOG.md","./LICENSE":"./LICENSE","./README.md":"./README.md","./documentation/migrations/monster-5-options.md":"./documentation/migrations/monster-5-options.md","./package.json":"./package.json","./source/*":"./source/*","./test/*":"./test/*"},"files":["source","test/cases","test/util","documentation/migrations/monster-5-options.md","CHANGELOG.md"],"homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"sideEffects":["./source/monster.mjs","./source/components/**","./source/i18n/**","./test/**"],"type":"module","version":"5.1.0"}
@@ -0,0 +1,2 @@
1
+ /** generated from surface.pcss **/
2
+ :where(.monster-surface){box-sizing:border-box;min-inline-size:0;display:grid;gap:var(--monster-surface-gap,var(--monster-space-3));padding:var(--monster-surface-padding,var(--monster-space-5));border-width:var(--monster-border-width);border-style:var(--monster-border-style);border-color:var(--monster-color-border-1);border-radius:var(--monster-border-radius);box-shadow:var(--monster-box-shadow-1)}.monster-surface-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--monster-surface-grid-min-inline-size,20rem)),1fr));gap:var(--monster-surface-grid-gap,var(--monster-space-5));align-items:stretch}.monster-surface-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--monster-surface-actions-gap,var(--monster-space-3));margin-block-start:auto}
@@ -0,0 +1,33 @@
1
+ :where(.monster-surface) {
2
+ box-sizing: border-box;
3
+ min-inline-size: 0;
4
+ display: grid;
5
+ gap: var(--monster-surface-gap, var(--monster-space-3));
6
+ padding: var(--monster-surface-padding, var(--monster-space-5));
7
+ border-width: var(--monster-border-width);
8
+ border-style: var(--monster-border-style);
9
+ border-color: var(--monster-color-border-1);
10
+ border-radius: var(--monster-border-radius);
11
+ box-shadow: var(--monster-box-shadow-1);
12
+ }
13
+
14
+ .monster-surface-grid {
15
+ display: grid;
16
+ grid-template-columns: repeat(
17
+ auto-fit,
18
+ minmax(
19
+ min(100%, var(--monster-surface-grid-min-inline-size, 20rem)),
20
+ 1fr
21
+ )
22
+ );
23
+ gap: var(--monster-surface-grid-gap, var(--monster-space-5));
24
+ align-items: stretch;
25
+ }
26
+
27
+ .monster-surface-actions {
28
+ display: flex;
29
+ flex-wrap: wrap;
30
+ align-items: center;
31
+ gap: var(--monster-surface-actions-gap, var(--monster-space-3));
32
+ margin-block-start: auto;
33
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright © Volker Schukai and all contributing authors, 2026. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact Volker Schukai.
11
+ */
12
+
13
+ import { addAttributeToken } from "../../dom/attributes.mjs";
14
+ import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
15
+
16
+ export { SurfaceStyleSheet };
17
+
18
+ /**
19
+ * @private
20
+ * @type {CSSStyleSheet}
21
+ */
22
+ const SurfaceStyleSheet = new CSSStyleSheet();
23
+
24
+ try {
25
+ SurfaceStyleSheet.insertRule(
26
+ `
27
+ @layer surface {
28
+ :where(.monster-surface){box-sizing:border-box;min-inline-size:0;display:grid;gap:var(--monster-surface-gap,var(--monster-space-3));padding:var(--monster-surface-padding,var(--monster-space-5));border-width:var(--monster-border-width);border-style:var(--monster-border-style);border-color:var(--monster-color-border-1);border-radius:var(--monster-border-radius);box-shadow:var(--monster-box-shadow-1)}.monster-surface-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--monster-surface-grid-min-inline-size,20rem)),1fr));gap:var(--monster-surface-grid-gap,var(--monster-space-5));align-items:stretch}.monster-surface-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--monster-surface-actions-gap,var(--monster-space-3));margin-block-start:auto}
29
+ }`,
30
+ 0,
31
+ );
32
+ } catch (e) {
33
+ addAttributeToken(
34
+ document.getRootNode().querySelector("html"),
35
+ ATTRIBUTE_ERRORMESSAGE,
36
+ e + "",
37
+ );
38
+ }
@@ -156,7 +156,7 @@ function getMonsterVersion() {
156
156
  }
157
157
 
158
158
  /** Managed by the release script; keep synchronized with package.json. */
159
- monsterVersion = new Version("5.0.0");
159
+ monsterVersion = new Version("5.1.0");
160
160
 
161
161
  return monsterVersion;
162
162
  }
@@ -0,0 +1,50 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { expect } from "chai";
3
+
4
+ const source = readFileSync(
5
+ new URL("../../../../source/components/style/surface.pcss", import.meta.url),
6
+ "utf8",
7
+ );
8
+ const generatedStyleSheet = readFileSync(
9
+ new URL(
10
+ "../../../../source/components/stylesheet/surface.mjs",
11
+ import.meta.url,
12
+ ),
13
+ "utf8",
14
+ );
15
+
16
+ describe("Content surfaces", () => {
17
+ it("provides theme-composable static content geometry", () => {
18
+ expect(source).to.include(":where(.monster-surface)");
19
+ expect(source).to.include(
20
+ "gap: var(--monster-surface-gap, var(--monster-space-3));",
21
+ );
22
+ expect(source).to.include(
23
+ "padding: var(--monster-surface-padding, var(--monster-space-5));",
24
+ );
25
+ expect(source).to.include("border-width: var(--monster-border-width);");
26
+ expect(source).to.include("border-style: var(--monster-border-style);");
27
+ expect(source).to.include("border-color: var(--monster-color-border-1);");
28
+ expect(source).to.include("border-radius: var(--monster-border-radius);");
29
+ expect(source).not.to.match(/^\s*(?:background-color|color):/mu);
30
+ });
31
+
32
+ it("provides reusable responsive grids and action rows", () => {
33
+ expect(source).to.include(".monster-surface-grid");
34
+ expect(source).to.include("grid-template-columns: repeat(");
35
+ expect(source).to.include("auto-fit,");
36
+ expect(source).to.include(".monster-surface-actions");
37
+ expect(source).to.include("margin-block-start: auto;");
38
+ });
39
+
40
+ it("keeps the generated stylesheet synchronized", () => {
41
+ expect(generatedStyleSheet).to.include(":where(.monster-surface)");
42
+ expect(generatedStyleSheet).to.include(
43
+ "gap:var(--monster-surface-gap,var(--monster-space-3))",
44
+ );
45
+ expect(generatedStyleSheet).to.include(
46
+ "grid-template-columns:repeat(auto-fit,minmax(min(100%,var(--monster-surface-grid-min-inline-size,20rem)),1fr))",
47
+ );
48
+ expect(generatedStyleSheet).to.include(".monster-surface-actions");
49
+ });
50
+ });
@@ -27,6 +27,10 @@ function readBuildNix() {
27
27
  return readFileSync(join(projectRoot, "nix/scripts/build.nix"), "utf8");
28
28
  }
29
29
 
30
+ function readProjectFile(path) {
31
+ return readFileSync(join(projectRoot, path), "utf8");
32
+ }
33
+
30
34
  describe("Packaging issue #577", function () {
31
35
  describe("exports map", function () {
32
36
  it("should resolve every literal export target to an existing file", function () {
@@ -161,6 +165,62 @@ describe("Packaging issue #577", function () {
161
165
  ).to.be.true;
162
166
  }
163
167
  });
168
+
169
+ it("should ship and export the Monster 5 migration guide", function () {
170
+ const migrationPath = "documentation/migrations/monster-5-options.md";
171
+ const pkg = readPackageJson();
172
+ const buildNix = readBuildNix();
173
+
174
+ expect(pkg.files).to.include(migrationPath);
175
+ expect(pkg.exports[`./${migrationPath}`]).to.equal(`./${migrationPath}`);
176
+ expect(existsSync(join(projectRoot, migrationPath))).to.be.true;
177
+ expect(buildNix).to.include(migrationPath);
178
+ expect(buildNix).to.include(`package/${migrationPath}`);
179
+ });
180
+ });
181
+
182
+ describe("Monster 5 migration contract", function () {
183
+ it("should document the strict lifecycle and migration phases", function () {
184
+ const guide = readProjectFile(
185
+ "documentation/migrations/monster-5-options.md",
186
+ );
187
+
188
+ for (const contract of [
189
+ "OptionsNotInitializedError",
190
+ "getOption()",
191
+ "setOption()",
192
+ "setOptions()",
193
+ "defaults",
194
+ "customization",
195
+ "document.createElement()",
196
+ "initMethodSymbol",
197
+ "assembleMethodSymbol",
198
+ "There is no compatibility switch",
199
+ ]) {
200
+ expect(guide, `migration guide is missing ${contract}`).to.include(
201
+ contract,
202
+ );
203
+ }
204
+ });
205
+
206
+ it("should describe the breaking change in the 5.0.0 changelog", function () {
207
+ const changelog = readProjectFile("CHANGELOG.md");
208
+ const release = changelog
209
+ .split("## [5.0.0]")[1]
210
+ .split("## [4.154.12]")[0];
211
+
212
+ for (const contract of [
213
+ "### Breaking Changes",
214
+ "OptionsNotInitializedError",
215
+ "initMethodSymbol",
216
+ "assembleMethodSymbol",
217
+ "documentation/migrations/monster-5-options.md",
218
+ ]) {
219
+ expect(release, `5.0.0 changelog is missing ${contract}`).to.include(
220
+ contract,
221
+ );
222
+ }
223
+ });
164
224
  });
165
225
 
166
226
  describe("generated barrel", function () {