@schukai/monster 3.73.4 → 3.73.5

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -2,13 +2,17 @@
2
2
 
3
3
 
4
4
 
5
- ## [3.73.4] - 2024-07-02
5
+ ## [3.73.5] - 2024-07-02
6
6
 
7
7
  ### Bug Fixes
8
8
 
9
- - eventprocessing is now only active in selected controls: form, filter. [#224](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/224)
9
+ - originValues in the savebutton is now reset.
10
+
11
+ ## [3.73.4] - 2024-07-02
10
12
 
13
+ ### Bug Fixes
11
14
 
15
+ - event processing is now only active in selected controls: form, filter. [#224](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/224)
12
16
 
13
17
  ## [3.73.3] - 2024-07-01
14
18
 
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.7","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","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"},"type":"module","version":"3.73.4"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.7","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","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"},"type":"module","version":"3.73.5"}
@@ -12,33 +12,33 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { instanceSymbol, internalSymbol } from "../../constants.mjs";
16
- import { diff } from "../../data/diff.mjs";
17
- import { addAttributeToken } from "../../dom/attributes.mjs";
18
- import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
15
+ import {instanceSymbol, internalSymbol} from "../../constants.mjs";
16
+ import {diff} from "../../data/diff.mjs";
17
+ import {addAttributeToken} from "../../dom/attributes.mjs";
18
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../dom/constants.mjs";
19
19
  import {
20
- assembleMethodSymbol,
21
- CustomElement,
22
- attributeObserverSymbol,
23
- registerCustomElement,
20
+ assembleMethodSymbol,
21
+ CustomElement,
22
+ attributeObserverSymbol,
23
+ registerCustomElement,
24
24
  } from "../../dom/customelement.mjs";
25
- import { findElementWithSelectorUpwards } from "../../dom/util.mjs";
26
- import { isString, isArray } from "../../types/is.mjs";
27
- import { Observer } from "../../types/observer.mjs";
28
- import { TokenList } from "../../types/tokenlist.mjs";
29
- import { clone } from "../../util/clone.mjs";
30
- import { State } from "../form/types/state.mjs";
31
- import { ATTRIBUTE_DATASOURCE_SELECTOR } from "./constants.mjs";
32
- import { Datasource } from "./datasource.mjs";
33
- import { BadgeStyleSheet } from "../stylesheet/badge.mjs";
34
- import { SaveButtonStyleSheet } from "./stylesheet/save-button.mjs";
25
+ import {findElementWithSelectorUpwards} from "../../dom/util.mjs";
26
+ import {isString, isArray} from "../../types/is.mjs";
27
+ import {Observer} from "../../types/observer.mjs";
28
+ import {TokenList} from "../../types/tokenlist.mjs";
29
+ import {clone} from "../../util/clone.mjs";
30
+ import {State} from "../form/types/state.mjs";
31
+ import {ATTRIBUTE_DATASOURCE_SELECTOR} from "./constants.mjs";
32
+ import {Datasource} from "./datasource.mjs";
33
+ import {BadgeStyleSheet} from "../stylesheet/badge.mjs";
34
+ import {SaveButtonStyleSheet} from "./stylesheet/save-button.mjs";
35
35
 
36
36
  import {
37
- handleDataSourceChanges,
38
- datasourceLinkedElementSymbol,
37
+ handleDataSourceChanges,
38
+ datasourceLinkedElementSymbol,
39
39
  } from "./util.mjs";
40
40
 
41
- export { SaveButton };
41
+ export {SaveButton};
42
42
 
43
43
  /**
44
44
  * @private
@@ -46,6 +46,12 @@ export { SaveButton };
46
46
  */
47
47
  const stateButtonElementSymbol = Symbol("stateButtonElement");
48
48
 
49
+ /**
50
+ * @private
51
+ * @type {symbol}
52
+ */
53
+ const originValuesSymbol = Symbol("originValues");
54
+
49
55
  /**
50
56
  * @private
51
57
  * @type {symbol}
@@ -53,185 +59,185 @@ const stateButtonElementSymbol = Symbol("stateButtonElement");
53
59
  const badgeElementSymbol = Symbol("badgeElement");
54
60
 
55
61
  class SaveButton extends CustomElement {
56
- /**
57
- * This method is called by the `instanceof` operator.
58
- * @returns {symbol}
59
- */
60
- static get [instanceSymbol]() {
61
- return Symbol.for(
62
- "@schukai/monster/components/datasource/save-button@@instance",
63
- );
64
- }
65
-
66
- /**
67
- * To set the options via the html tag the attribute `data-monster-options` must be used.
68
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
69
- *
70
- * The individual configuration values can be found in the table.
71
- *
72
- * @property {Object} templates Template definitions
73
- * @property {string} templates.main Main template
74
- * @property {object} datasource The datasource
75
- * @property {string} datasource.selector The selector of the datasource
76
- * @property {string} labels.button The button label
77
- * @property {Object} classes The classes
78
- * @property {string} classes.bar The bar class
79
- * @property {string} classes.badge The badge class
80
- * @property {Array} ignoreChanges The ignore changes (regex)
81
- * @property {Array} data The data
82
- * @return {Object}
83
- */
84
- get defaults() {
85
- const obj = Object.assign({}, super.defaults, {
86
- templates: {
87
- main: getTemplate(),
88
- },
89
-
90
- labels: {
91
- button: "save",
92
- },
93
-
94
- classes: {
95
- bar: "monster-button-primary",
96
- badge: "monster-badge-secondary hidden",
97
- },
98
-
99
- datasource: {
100
- selector: null,
101
- },
102
-
103
- changes: "0",
104
-
105
- ignoreChanges: [],
106
-
107
- data: {},
108
-
109
- disabled: false,
110
- });
111
-
112
- updateOptionsFromArguments.call(this, obj);
113
- return obj;
114
- }
115
-
116
- /**
117
- *
118
- * @return {string}
119
- */
120
- static getTag() {
121
- return "monster-datasource-save-button";
122
- }
123
-
124
- /**
125
- * This method is responsible for assembling the component.
126
- *
127
- * It calls the parent's assemble method first, then initializes control references and event handlers.
128
- * If the `datasource.selector` option is provided and is a string, it searches for the corresponding
129
- * element in the DOM using that selector.
130
- *
131
- * If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class.
132
- *
133
- * If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component
134
- * attaches an observer to the datasource's changes.
135
- *
136
- * The observer is a function that calls the `handleDataSourceChanges` method in the context of the component.
137
- * Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges`
138
- * method in the component's context.
139
- */
140
- [assembleMethodSymbol]() {
141
- super[assembleMethodSymbol]();
142
- const self = this;
143
-
144
- initControlReferences.call(this);
145
- initEventHandler.call(this);
146
-
147
- const selector = this.getOption("datasource.selector");
148
-
149
- if (isString(selector)) {
150
- const element = findElementWithSelectorUpwards(this, selector);
151
- if (element === null) {
152
- throw new Error("the selector must match exactly one element");
153
- }
154
-
155
- if (!(element instanceof Datasource)) {
156
- throw new TypeError("the element must be a datasource");
157
- }
158
-
159
- this[datasourceLinkedElementSymbol] = element;
160
- element.datasource.attachObserver(
161
- new Observer(handleDataSourceChanges.bind(this)),
162
- );
163
-
164
- let originValues;
165
-
166
- element.datasource.attachObserver(
167
- new Observer(function () {
168
- if (!originValues) {
169
- originValues = clone(self[datasourceLinkedElementSymbol].data);
170
- }
171
-
172
- const currentValues = this.getRealSubject();
173
- const ignoreChanges = self.getOption("ignoreChanges");
174
-
175
- const result = diff(originValues, currentValues);
176
- if (isArray(ignoreChanges) && ignoreChanges.length > 0) {
177
- const itemsToRemove = [];
178
- for (const item of result) {
179
- for (const ignorePattern of ignoreChanges) {
180
- const p = new RegExp(ignorePattern);
181
- if (p.test(item.path)) {
182
- itemsToRemove.push(item);
183
- break;
184
- }
185
- }
186
- }
187
-
188
- for (const itemToRemove of itemsToRemove) {
189
- const index = result.indexOf(itemToRemove);
190
- if (index > -1) {
191
- result.splice(index, 1);
192
- }
193
- }
194
- }
195
-
196
- if (isArray(result) && result.length > 0) {
197
- self[stateButtonElementSymbol].setState("changed");
198
- self[stateButtonElementSymbol].setOption("disabled", false);
199
- self.setOption("changes", result.length);
200
- self.setOption(
201
- "classes.badge",
202
- new TokenList(self.getOption("classes.badge"))
203
- .remove("hidden")
204
- .toString(),
205
- );
206
- } else {
207
- self[stateButtonElementSymbol].removeState();
208
- self[stateButtonElementSymbol].setOption("disabled", true);
209
- self.setOption("changes", 0);
210
- self.setOption(
211
- "classes.badge",
212
- new TokenList(self.getOption("classes.badge"))
213
- .add("hidden")
214
- .toString(),
215
- );
216
- }
217
- }),
218
- );
219
- }
220
-
221
- this.attachObserver(
222
- new Observer(() => {
223
- handleDataSourceChanges.call(this);
224
- }),
225
- );
226
- }
227
-
228
- /**
229
- *
230
- * @return [CSSStyleSheet]
231
- */
232
- static getCSSStyleSheet() {
233
- return [SaveButtonStyleSheet, BadgeStyleSheet];
234
- }
62
+ /**
63
+ * This method is called by the `instanceof` operator.
64
+ * @returns {symbol}
65
+ */
66
+ static get [instanceSymbol]() {
67
+ return Symbol.for(
68
+ "@schukai/monster/components/datasource/save-button@@instance",
69
+ );
70
+ }
71
+
72
+ /**
73
+ * To set the options via the html tag the attribute `data-monster-options` must be used.
74
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
75
+ *
76
+ * The individual configuration values can be found in the table.
77
+ *
78
+ * @property {Object} templates Template definitions
79
+ * @property {string} templates.main Main template
80
+ * @property {object} datasource The datasource
81
+ * @property {string} datasource.selector The selector of the datasource
82
+ * @property {string} labels.button The button label
83
+ * @property {Object} classes The classes
84
+ * @property {string} classes.bar The bar class
85
+ * @property {string} classes.badge The badge class
86
+ * @property {Array} ignoreChanges The ignore changes (regex)
87
+ * @property {Array} data The data
88
+ * @return {Object}
89
+ */
90
+ get defaults() {
91
+ const obj = Object.assign({}, super.defaults, {
92
+ templates: {
93
+ main: getTemplate(),
94
+ },
95
+
96
+ labels: {
97
+ button: "save",
98
+ },
99
+
100
+ classes: {
101
+ bar: "monster-button-primary",
102
+ badge: "monster-badge-secondary hidden",
103
+ },
104
+
105
+ datasource: {
106
+ selector: null,
107
+ },
108
+
109
+ changes: "0",
110
+
111
+ ignoreChanges: [],
112
+
113
+ data: {},
114
+
115
+ disabled: false,
116
+ });
117
+
118
+ updateOptionsFromArguments.call(this, obj);
119
+ return obj;
120
+ }
121
+
122
+ /**
123
+ *
124
+ * @return {string}
125
+ */
126
+ static getTag() {
127
+ return "monster-datasource-save-button";
128
+ }
129
+
130
+ /**
131
+ * This method is responsible for assembling the component.
132
+ *
133
+ * It calls the parent's assemble method first, then initializes control references and event handlers.
134
+ * If the `datasource.selector` option is provided and is a string, it searches for the corresponding
135
+ * element in the DOM using that selector.
136
+ *
137
+ * If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class.
138
+ *
139
+ * If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component
140
+ * attaches an observer to the datasource's changes.
141
+ *
142
+ * The observer is a function that calls the `handleDataSourceChanges` method in the context of the component.
143
+ * Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges`
144
+ * method in the component's context.
145
+ */
146
+ [assembleMethodSymbol]() {
147
+ super[assembleMethodSymbol]();
148
+ const self = this;
149
+
150
+ initControlReferences.call(this);
151
+ initEventHandler.call(this);
152
+
153
+ const selector = this.getOption("datasource.selector");
154
+
155
+ if (isString(selector)) {
156
+ const element = findElementWithSelectorUpwards(this, selector);
157
+ if (element === null) {
158
+ throw new Error("the selector must match exactly one element");
159
+ }
160
+
161
+ if (!(element instanceof Datasource)) {
162
+ throw new TypeError("the element must be a datasource");
163
+ }
164
+
165
+ this[datasourceLinkedElementSymbol] = element;
166
+ element.datasource.attachObserver(
167
+ new Observer(handleDataSourceChanges.bind(this)),
168
+ );
169
+
170
+ self[originValuesSymbol] = null;
171
+
172
+ element.datasource.attachObserver(
173
+ new Observer(function () {
174
+ if (!self[originValuesSymbol]) {
175
+ self[originValuesSymbol] = clone(self[datasourceLinkedElementSymbol].data);
176
+ }
177
+
178
+ const currentValues = this.getRealSubject();
179
+ const ignoreChanges = self.getOption("ignoreChanges");
180
+
181
+ const result = diff(self[originValuesSymbol], currentValues);
182
+ if (isArray(ignoreChanges) && ignoreChanges.length > 0) {
183
+ const itemsToRemove = [];
184
+ for (const item of result) {
185
+ for (const ignorePattern of ignoreChanges) {
186
+ const p = new RegExp(ignorePattern);
187
+ if (p.test(item.path)) {
188
+ itemsToRemove.push(item);
189
+ break;
190
+ }
191
+ }
192
+ }
193
+
194
+ for (const itemToRemove of itemsToRemove) {
195
+ const index = result.indexOf(itemToRemove);
196
+ if (index > -1) {
197
+ result.splice(index, 1);
198
+ }
199
+ }
200
+ }
201
+
202
+ if (isArray(result) && result.length > 0) {
203
+ self[stateButtonElementSymbol].setState("changed");
204
+ self[stateButtonElementSymbol].setOption("disabled", false);
205
+ self.setOption("changes", result.length);
206
+ self.setOption(
207
+ "classes.badge",
208
+ new TokenList(self.getOption("classes.badge"))
209
+ .remove("hidden")
210
+ .toString(),
211
+ );
212
+ } else {
213
+ self[stateButtonElementSymbol].removeState();
214
+ self[stateButtonElementSymbol].setOption("disabled", true);
215
+ self.setOption("changes", 0);
216
+ self.setOption(
217
+ "classes.badge",
218
+ new TokenList(self.getOption("classes.badge"))
219
+ .add("hidden")
220
+ .toString(),
221
+ );
222
+ }
223
+ }),
224
+ );
225
+ }
226
+
227
+ this.attachObserver(
228
+ new Observer(() => {
229
+ handleDataSourceChanges.call(this);
230
+ }),
231
+ );
232
+ }
233
+
234
+ /**
235
+ *
236
+ * @return [CSSStyleSheet]
237
+ */
238
+ static getCSSStyleSheet() {
239
+ return [SaveButtonStyleSheet, BadgeStyleSheet];
240
+ }
235
241
  }
236
242
 
237
243
  /**
@@ -239,77 +245,78 @@ class SaveButton extends CustomElement {
239
245
  * @return {Monster.Components.Datatable.Form}
240
246
  */
241
247
  function initControlReferences() {
242
- if (!this.shadowRoot) {
243
- throw new Error("no shadow-root is defined");
244
- }
245
-
246
- this[stateButtonElementSymbol] = this.shadowRoot.querySelector(
247
- "[data-monster-role=state-button]",
248
- );
249
-
250
- this[badgeElementSymbol] = this.shadowRoot.querySelector(
251
- "[data-monster-role=badge]",
252
- );
253
-
254
- if (this[stateButtonElementSymbol]) {
255
- setTimeout(() => {
256
- const states = {
257
- changed: new State(
258
- "changed",
259
- '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-arrow-up" viewBox="0 0 16 16">\n' +
260
- ' <path fill-rule="evenodd" d="M7.646 5.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708z"/>\n' +
261
- ' <path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383m.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/>\n' +
262
- "</svg>",
263
- ),
264
- };
265
-
266
- this[stateButtonElementSymbol].removeState();
267
- this[stateButtonElementSymbol].setOption("disabled", "disabled");
268
- this[stateButtonElementSymbol].setOption("states", states);
269
- this[stateButtonElementSymbol].setOption(
270
- "labels.button",
271
- this.getOption("labels.button"),
272
- );
273
- }, 1);
274
- }
275
-
276
- return this;
248
+ if (!this.shadowRoot) {
249
+ throw new Error("no shadow-root is defined");
250
+ }
251
+
252
+ this[stateButtonElementSymbol] = this.shadowRoot.querySelector(
253
+ "[data-monster-role=state-button]",
254
+ );
255
+
256
+ this[badgeElementSymbol] = this.shadowRoot.querySelector(
257
+ "[data-monster-role=badge]",
258
+ );
259
+
260
+ if (this[stateButtonElementSymbol]) {
261
+ setTimeout(() => {
262
+ const states = {
263
+ changed: new State(
264
+ "changed",
265
+ '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-arrow-up" viewBox="0 0 16 16">\n' +
266
+ ' <path fill-rule="evenodd" d="M7.646 5.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708z"/>\n' +
267
+ ' <path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383m.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/>\n' +
268
+ "</svg>",
269
+ ),
270
+ };
271
+
272
+ this[stateButtonElementSymbol].removeState();
273
+ this[stateButtonElementSymbol].setOption("disabled", "disabled");
274
+ this[stateButtonElementSymbol].setOption("states", states);
275
+ this[stateButtonElementSymbol].setOption(
276
+ "labels.button",
277
+ this.getOption("labels.button"),
278
+ );
279
+ }, 1);
280
+ }
281
+
282
+ return this;
277
283
  }
278
284
 
279
285
  /**
280
286
  * @private
281
287
  */
282
288
  function initEventHandler() {
283
- setTimeout(() => {
284
- this[stateButtonElementSymbol].setOption("actions.click", () => {
285
- this[datasourceLinkedElementSymbol]
286
- .write()
287
- .then(() => {
288
- this[stateButtonElementSymbol].removeState();
289
- this[stateButtonElementSymbol].setOption("disabled", true);
290
- this.setOption("changes", 0);
291
- this.setOption(
292
- "classes.badge",
293
- new TokenList(this.getOption("classes.badge"))
294
- .add("hidden")
295
- .toString(),
296
- );
297
- })
298
- .catch((error) => {
299
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
300
- });
301
- });
302
- }, 1);
289
+ setTimeout(() => {
290
+ this[stateButtonElementSymbol].setOption("actions.click", () => {
291
+ this[datasourceLinkedElementSymbol]
292
+ .write()
293
+ .then(() => {
294
+ this[originValuesSymbol] = null;
295
+ this[stateButtonElementSymbol].removeState();
296
+ this[stateButtonElementSymbol].setOption("disabled", true);
297
+ this.setOption("changes", 0);
298
+ this.setOption(
299
+ "classes.badge",
300
+ new TokenList(this.getOption("classes.badge"))
301
+ .add("hidden")
302
+ .toString(),
303
+ );
304
+ })
305
+ .catch((error) => {
306
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
307
+ });
308
+ });
309
+ }, 1);
303
310
  }
304
311
 
305
312
  /**
306
313
  * @param {Object} options
307
314
  */
308
315
  function updateOptionsFromArguments(options) {
309
- const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
310
- if (selector) {
311
- options.datasource.selector = selector;
312
- }
316
+ const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
317
+ if (selector) {
318
+ options.datasource.selector = selector;
319
+ }
313
320
  }
314
321
 
315
322
  /**
@@ -317,8 +324,8 @@ function updateOptionsFromArguments(options) {
317
324
  * @return {string}
318
325
  */
319
326
  function getTemplate() {
320
- // language=HTML
321
- return `
327
+ // language=HTML
328
+ return `
322
329
  <div data-monster-role="control" part="control"
323
330
  data-monster-attributes="disabled path:disabled | if:true">
324
331
  <monster-state-button data-monster-role="state-button">save</monster-state-button>