@schukai/monster 4.120.0 → 4.121.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 +16 -0
- package/package.json +1 -1
- package/source/components/content/viewer.mjs +113 -9
- package/source/components/form/popper-button.mjs +43 -7
- package/source/components/form/select.mjs +78 -11
- package/source/components/host/host.mjs +371 -0
- package/source/components/layout/popper.mjs +91 -7
- package/source/types/version.mjs +1 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +14 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
## [4.121.0] - 2026-02-13
|
|
6
|
+
|
|
7
|
+
### Add Features
|
|
8
|
+
|
|
9
|
+
- Enhance dismissal functionality for Popper and Select components
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [4.120.1] - 2026-02-12
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- new setContent/setPDF API for MonsterViewer
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
5
21
|
## [4.120.0] - 2026-02-03
|
|
6
22
|
|
|
7
23
|
### Add Features
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.4","@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":"4.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.4","@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":"4.121.0"}
|
|
@@ -138,7 +138,14 @@ class Viewer extends CustomElement {
|
|
|
138
138
|
/**
|
|
139
139
|
* Sets the content of an element based on the provided content and media type.
|
|
140
140
|
*
|
|
141
|
-
*
|
|
141
|
+
* Supported forms:
|
|
142
|
+
* - setContent(contentString, mediaType)
|
|
143
|
+
* - setContent({ mediaType, data, encoding })
|
|
144
|
+
*
|
|
145
|
+
* The declarative form requires an explicit `encoding` when `data` is a string.
|
|
146
|
+
* Use `encoding: "url"` for URLs and `encoding: "base64"` for base64 payloads.
|
|
147
|
+
*
|
|
148
|
+
* @param {string|object} content - The content to be set or a declarative payload.
|
|
142
149
|
* @param {string} [mediaType="text/plain"] - The media type of the content. Defaults to "text/plain" if not specified.
|
|
143
150
|
* @return {void} This method does not return a value.
|
|
144
151
|
* @throws {Error} Throws an error if shadowRoot is not defined.
|
|
@@ -148,6 +155,44 @@ class Viewer extends CustomElement {
|
|
|
148
155
|
throw new Error("no shadow-root is defined");
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
let declarativePayload = null;
|
|
159
|
+
// Declarative form: { mediaType, data, encoding }
|
|
160
|
+
if (
|
|
161
|
+
content &&
|
|
162
|
+
typeof content === "object" &&
|
|
163
|
+
!isBlob(content) &&
|
|
164
|
+
!(content instanceof URL)
|
|
165
|
+
) {
|
|
166
|
+
const { mediaType: mt, data, encoding } = content;
|
|
167
|
+
mediaType = mt ?? mediaType ?? "text/plain";
|
|
168
|
+
declarativePayload = { data, encoding };
|
|
169
|
+
|
|
170
|
+
if (isString(data)) {
|
|
171
|
+
if (!encoding) {
|
|
172
|
+
this.dispatchEvent(
|
|
173
|
+
new CustomEvent("viewer-error", {
|
|
174
|
+
detail: "String content requires explicit encoding",
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (encoding === "base64") {
|
|
180
|
+
content = data;
|
|
181
|
+
} else if (encoding === "url") {
|
|
182
|
+
content = data;
|
|
183
|
+
} else {
|
|
184
|
+
this.dispatchEvent(
|
|
185
|
+
new CustomEvent("viewer-error", {
|
|
186
|
+
detail: "Invalid encoding",
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
content = data;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
151
196
|
const renderers = this.getOption("renderers");
|
|
152
197
|
|
|
153
198
|
const isDataURL = (value) => {
|
|
@@ -235,7 +280,10 @@ class Viewer extends CustomElement {
|
|
|
235
280
|
switch (mediaTypeObject.subtype) {
|
|
236
281
|
case "pdf":
|
|
237
282
|
if (checkRenderer("pdf", mediaTypeObject.toString())) {
|
|
238
|
-
renderers.pdf.call(
|
|
283
|
+
renderers.pdf.call(
|
|
284
|
+
this,
|
|
285
|
+
declarativePayload ? declarativePayload : content,
|
|
286
|
+
);
|
|
239
287
|
}
|
|
240
288
|
break;
|
|
241
289
|
|
|
@@ -426,13 +474,63 @@ class Viewer extends CustomElement {
|
|
|
426
474
|
/**
|
|
427
475
|
* Configures and embeds a PDF document into the application with customizable display settings.
|
|
428
476
|
*
|
|
429
|
-
*
|
|
477
|
+
* Supported forms:
|
|
478
|
+
* - setPDF(blobOrUrlOrBase64String, navigation, toolbar, scrollbar)
|
|
479
|
+
* - setPDF({ data, encoding, navigation, toolbar, scrollbar })
|
|
480
|
+
*
|
|
481
|
+
* The declarative form requires an explicit `encoding` when `data` is a string.
|
|
482
|
+
* Use `encoding: "url"` for URLs and `encoding: "base64"` for base64 payloads.
|
|
483
|
+
*
|
|
484
|
+
* @param {Blob|URL|string|object} data The PDF data to be embedded. Can be provided as a Blob, URL, or base64 string,
|
|
485
|
+
* or a declarative payload: { data, encoding, navigation, toolbar, scrollbar }.
|
|
430
486
|
* @param {boolean} [navigation=true] Determines whether the navigation pane is displayed in the PDF viewer.
|
|
431
487
|
* @param {boolean} [toolbar=true] Controls the visibility of the toolbar in the PDF viewer.
|
|
432
488
|
* @param {boolean} [scrollbar=false] Configures the display of the scrollbar in the PDF viewer.
|
|
433
489
|
* @return {void} This method returns nothing but sets the embedded PDF as the content.
|
|
434
490
|
*/
|
|
435
491
|
setPDF(data, navigation = true, toolbar = true, scrollbar = false) {
|
|
492
|
+
let stringEncoding = null;
|
|
493
|
+
// Declarative form: { data, encoding, navigation, toolbar, scrollbar }
|
|
494
|
+
if (
|
|
495
|
+
data &&
|
|
496
|
+
typeof data === "object" &&
|
|
497
|
+
!isBlob(data) &&
|
|
498
|
+
!(data instanceof URL)
|
|
499
|
+
) {
|
|
500
|
+
const payload = data;
|
|
501
|
+
data = payload.data;
|
|
502
|
+
stringEncoding = payload.encoding ?? null;
|
|
503
|
+
if (typeof payload.navigation === "boolean") {
|
|
504
|
+
navigation = payload.navigation;
|
|
505
|
+
}
|
|
506
|
+
if (typeof payload.toolbar === "boolean") {
|
|
507
|
+
toolbar = payload.toolbar;
|
|
508
|
+
}
|
|
509
|
+
if (typeof payload.scrollbar === "boolean") {
|
|
510
|
+
scrollbar = payload.scrollbar;
|
|
511
|
+
}
|
|
512
|
+
if (isString(data)) {
|
|
513
|
+
if (!payload.encoding) {
|
|
514
|
+
this.dispatchEvent(
|
|
515
|
+
new CustomEvent("viewer-error", {
|
|
516
|
+
detail: "String PDF data requires explicit encoding",
|
|
517
|
+
}),
|
|
518
|
+
);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (payload.encoding === "base64") {
|
|
522
|
+
// handled below as string/base64
|
|
523
|
+
} else if (payload.encoding === "url") {
|
|
524
|
+
// handled below as string/url
|
|
525
|
+
} else {
|
|
526
|
+
this.dispatchEvent(
|
|
527
|
+
new CustomEvent("viewer-error", { detail: "Invalid encoding" }),
|
|
528
|
+
);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
436
534
|
const hashes =
|
|
437
535
|
"#toolbar=" +
|
|
438
536
|
(toolbar ? "1" : "0") +
|
|
@@ -440,7 +538,6 @@ class Viewer extends CustomElement {
|
|
|
440
538
|
(navigation ? "1" : "0") +
|
|
441
539
|
"&scrollbar=" +
|
|
442
540
|
(scrollbar ? "1" : "0");
|
|
443
|
-
|
|
444
541
|
let pdfURL = "";
|
|
445
542
|
if (isBlob(data)) {
|
|
446
543
|
pdfURL = URL.createObjectURL(data);
|
|
@@ -453,11 +550,18 @@ class Viewer extends CustomElement {
|
|
|
453
550
|
pdfURL = data.toString();
|
|
454
551
|
}
|
|
455
552
|
} else if (isString(data)) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
553
|
+
if (stringEncoding === "url") {
|
|
554
|
+
if (data.indexOf("#") === -1) {
|
|
555
|
+
pdfURL = data + hashes;
|
|
556
|
+
} else {
|
|
557
|
+
pdfURL = data;
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
// Default legacy behavior: treat string as base64
|
|
561
|
+
const blobObj = new Blob([atob(data)], { type: "application/pdf" });
|
|
562
|
+
const url = window.URL.createObjectURL(blobObj);
|
|
563
|
+
pdfURL = url + hashes;
|
|
564
|
+
}
|
|
461
565
|
} else {
|
|
462
566
|
this.dispatchEvent(
|
|
463
567
|
new CustomEvent("viewer-error", { detail: "Blob or URL expected" }),
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
assembleMethodSymbol,
|
|
19
19
|
registerCustomElement,
|
|
20
20
|
} from "../../dom/customelement.mjs";
|
|
21
|
-
import { getDocument } from "../../dom/util.mjs";
|
|
21
|
+
import { findElementWithSelectorUpwards, getDocument } from "../../dom/util.mjs";
|
|
22
22
|
import { isFunction } from "../../types/is.mjs";
|
|
23
23
|
import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
|
|
24
24
|
import { Popper } from "../layout/popper.mjs";
|
|
@@ -77,6 +77,20 @@ const popperElementSymbol = Symbol("popperElement");
|
|
|
77
77
|
*/
|
|
78
78
|
const arrowElementSymbol = Symbol("arrowElement");
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* local symbol
|
|
82
|
+
* @private
|
|
83
|
+
* @type {symbol}
|
|
84
|
+
*/
|
|
85
|
+
const hostElementSymbol = Symbol("hostElement");
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* local symbol
|
|
89
|
+
* @private
|
|
90
|
+
* @type {symbol}
|
|
91
|
+
*/
|
|
92
|
+
const usesHostDismissSymbol = Symbol("usesHostDismiss");
|
|
93
|
+
|
|
80
94
|
/**
|
|
81
95
|
* A beautiful popper button that can make your life easier and also looks good.
|
|
82
96
|
*
|
|
@@ -149,6 +163,10 @@ class PopperButton extends Popper {
|
|
|
149
163
|
role: null,
|
|
150
164
|
label: null,
|
|
151
165
|
},
|
|
166
|
+
|
|
167
|
+
features: {
|
|
168
|
+
closeOnOutsideClick: true,
|
|
169
|
+
},
|
|
152
170
|
});
|
|
153
171
|
}
|
|
154
172
|
|
|
@@ -187,11 +205,24 @@ class PopperButton extends Popper {
|
|
|
187
205
|
connectedCallback() {
|
|
188
206
|
super.connectedCallback();
|
|
189
207
|
|
|
208
|
+
this[hostElementSymbol] = findElementWithSelectorUpwards(
|
|
209
|
+
this,
|
|
210
|
+
"monster-host",
|
|
211
|
+
);
|
|
212
|
+
this[usesHostDismissSymbol] =
|
|
213
|
+
this[hostElementSymbol] &&
|
|
214
|
+
typeof this[hostElementSymbol].registerDismissable === "function";
|
|
215
|
+
|
|
190
216
|
const document = getDocument();
|
|
191
217
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
218
|
+
if (
|
|
219
|
+
this.getOption("features.closeOnOutsideClick", true) === true &&
|
|
220
|
+
!this[usesHostDismissSymbol]
|
|
221
|
+
) {
|
|
222
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
|
223
|
+
// close on outside ui-events
|
|
224
|
+
document.addEventListener(type, this[closeEventHandler]);
|
|
225
|
+
}
|
|
195
226
|
}
|
|
196
227
|
|
|
197
228
|
updatePopper.call(this);
|
|
@@ -204,9 +235,14 @@ class PopperButton extends Popper {
|
|
|
204
235
|
disconnectedCallback() {
|
|
205
236
|
super.disconnectedCallback();
|
|
206
237
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
238
|
+
if (
|
|
239
|
+
this.getOption("features.closeOnOutsideClick", true) === true &&
|
|
240
|
+
!this[usesHostDismissSymbol]
|
|
241
|
+
) {
|
|
242
|
+
// close on outside ui-events
|
|
243
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
|
244
|
+
document.removeEventListener(type, this[closeEventHandler]);
|
|
245
|
+
}
|
|
210
246
|
}
|
|
211
247
|
|
|
212
248
|
disconnectResizeObserver.call(this);
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
fireEvent,
|
|
39
39
|
} from "../../dom/events.mjs";
|
|
40
40
|
import { getLocaleOfDocument } from "../../dom/locale.mjs";
|
|
41
|
-
import { getDocument } from "../../dom/util.mjs";
|
|
41
|
+
import { findElementWithSelectorUpwards, getDocument } from "../../dom/util.mjs";
|
|
42
42
|
import {
|
|
43
43
|
getDocumentTranslations,
|
|
44
44
|
Translations,
|
|
@@ -105,6 +105,9 @@ const isLoadingSymbol = Symbol("isLoading");
|
|
|
105
105
|
* @type {Symbol}
|
|
106
106
|
*/
|
|
107
107
|
const closeEventHandler = Symbol("closeEventHandler");
|
|
108
|
+
const hostElementSymbol = Symbol("hostElement");
|
|
109
|
+
const dismissRecordSymbol = Symbol("dismissRecord");
|
|
110
|
+
const usesHostDismissSymbol = Symbol("usesHostDismiss");
|
|
108
111
|
|
|
109
112
|
/**
|
|
110
113
|
* local symbol
|
|
@@ -841,11 +844,20 @@ class Select extends CustomControl {
|
|
|
841
844
|
*/
|
|
842
845
|
connectedCallback() {
|
|
843
846
|
super.connectedCallback();
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
847
|
+
this[hostElementSymbol] = findElementWithSelectorUpwards(
|
|
848
|
+
this,
|
|
849
|
+
"monster-host",
|
|
850
|
+
);
|
|
851
|
+
this[usesHostDismissSymbol] =
|
|
852
|
+
this[hostElementSymbol] &&
|
|
853
|
+
typeof this[hostElementSymbol].registerDismissable === "function";
|
|
854
|
+
|
|
855
|
+
if (!this[usesHostDismissSymbol]) {
|
|
856
|
+
const document = getDocument();
|
|
857
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
|
858
|
+
// close on outside ui-events
|
|
859
|
+
document.addEventListener(type, this[closeEventHandler]);
|
|
860
|
+
}
|
|
849
861
|
}
|
|
850
862
|
|
|
851
863
|
parseSlotsToOptions.call(this);
|
|
@@ -873,13 +885,16 @@ class Select extends CustomControl {
|
|
|
873
885
|
*/
|
|
874
886
|
disconnectedCallback() {
|
|
875
887
|
super.disconnectedCallback();
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
888
|
+
if (!this[usesHostDismissSymbol]) {
|
|
889
|
+
const document = getDocument();
|
|
890
|
+
// close on outside ui-events
|
|
891
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
|
892
|
+
document.removeEventListener(type, this[closeEventHandler]);
|
|
893
|
+
}
|
|
881
894
|
}
|
|
882
895
|
|
|
896
|
+
unregisterFromHost.call(this);
|
|
897
|
+
|
|
883
898
|
disconnectResizeObserver.call(this);
|
|
884
899
|
}
|
|
885
900
|
|
|
@@ -3703,6 +3718,7 @@ function hide() {
|
|
|
3703
3718
|
this[popperElementSymbol].style.display = "none";
|
|
3704
3719
|
setStatusOrRemoveBadges.call(this, "closed");
|
|
3705
3720
|
removeAttributeToken(this[controlElementSymbol], "class", "open");
|
|
3721
|
+
unregisterFromHost.call(this);
|
|
3706
3722
|
}
|
|
3707
3723
|
|
|
3708
3724
|
/**
|
|
@@ -3772,6 +3788,7 @@ function show() {
|
|
|
3772
3788
|
setStatusOrRemoveBadges.call(this, "open");
|
|
3773
3789
|
|
|
3774
3790
|
addAttributeToken(this[controlElementSymbol], "class", "open");
|
|
3791
|
+
registerWithHost.call(this);
|
|
3775
3792
|
|
|
3776
3793
|
new Processing(() => {
|
|
3777
3794
|
if (!self?.[remoteFilterFirstOpendSymbol]) {
|
|
@@ -3798,6 +3815,56 @@ function show() {
|
|
|
3798
3815
|
});
|
|
3799
3816
|
}
|
|
3800
3817
|
|
|
3818
|
+
/**
|
|
3819
|
+
* @private
|
|
3820
|
+
*/
|
|
3821
|
+
function registerWithHost() {
|
|
3822
|
+
if (!this[usesHostDismissSymbol]) {
|
|
3823
|
+
return;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
if (!(this[hostElementSymbol] instanceof HTMLElement)) {
|
|
3827
|
+
return;
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
const record = this[hostElementSymbol].registerDismissable?.({
|
|
3831
|
+
element: this,
|
|
3832
|
+
owner: this,
|
|
3833
|
+
close: () => {
|
|
3834
|
+
hide.call(this);
|
|
3835
|
+
},
|
|
3836
|
+
priority: 20,
|
|
3837
|
+
options: {
|
|
3838
|
+
dismissOnOutside: true,
|
|
3839
|
+
},
|
|
3840
|
+
});
|
|
3841
|
+
|
|
3842
|
+
if (record) {
|
|
3843
|
+
this[dismissRecordSymbol] = record;
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
/**
|
|
3848
|
+
* @private
|
|
3849
|
+
*/
|
|
3850
|
+
function unregisterFromHost() {
|
|
3851
|
+
if (!this[usesHostDismissSymbol]) {
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
if (!(this[hostElementSymbol] instanceof HTMLElement)) {
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
if (this[dismissRecordSymbol]) {
|
|
3860
|
+
this[hostElementSymbol].unregisterDismissable?.(this[dismissRecordSymbol]);
|
|
3861
|
+
this[dismissRecordSymbol] = null;
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
this[hostElementSymbol].unregisterDismissable?.(this);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3801
3868
|
function initDefaultOptionsFromUrl() {
|
|
3802
3869
|
const url = this.getOption("filter.defaultOptionsUrl");
|
|
3803
3870
|
if (!url) {
|