@sv443-network/userutils 7.0.0 → 7.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.
@@ -8,7 +8,7 @@
8
8
  // ==UserLibrary==
9
9
  // @name UserUtils
10
10
  // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more
11
- // @version 7.0.0
11
+ // @version 7.1.0
12
12
  // @license MIT
13
13
  // @copyright Sv443 (https://github.com/Sv443)
14
14
 
@@ -552,6 +552,495 @@ Has: ${checksum}`);
552
552
  return [];
553
553
  }
554
554
 
555
+ // node_modules/nanoevents/index.js
556
+ var createNanoEvents = () => ({
557
+ emit(event, ...args) {
558
+ for (let i = 0, callbacks = this.events[event] || [], length = callbacks.length; i < length; i++) {
559
+ callbacks[i](...args);
560
+ }
561
+ },
562
+ events: {},
563
+ on(event, cb) {
564
+ var _a;
565
+ ((_a = this.events)[event] || (_a[event] = [])).push(cb);
566
+ return () => {
567
+ var _a2;
568
+ this.events[event] = (_a2 = this.events[event]) == null ? void 0 : _a2.filter((i) => cb !== i);
569
+ };
570
+ }
571
+ });
572
+
573
+ // lib/NanoEmitter.ts
574
+ var NanoEmitter = class {
575
+ constructor(options = {}) {
576
+ __publicField(this, "events", createNanoEvents());
577
+ __publicField(this, "eventUnsubscribes", []);
578
+ __publicField(this, "emitterOptions");
579
+ this.emitterOptions = __spreadValues({
580
+ publicEmit: false
581
+ }, options);
582
+ }
583
+ /** Subscribes to an event - returns a function that unsubscribes the event listener */
584
+ on(event, cb) {
585
+ let unsub;
586
+ const unsubProxy = () => {
587
+ if (!unsub)
588
+ return;
589
+ unsub();
590
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
591
+ };
592
+ unsub = this.events.on(event, cb);
593
+ this.eventUnsubscribes.push(unsub);
594
+ return unsubProxy;
595
+ }
596
+ /** Subscribes to an event and calls the callback or resolves the Promise only once */
597
+ once(event, cb) {
598
+ return new Promise((resolve) => {
599
+ let unsub;
600
+ const onceProxy = (...args) => {
601
+ unsub();
602
+ cb == null ? void 0 : cb(...args);
603
+ resolve(args);
604
+ };
605
+ unsub = this.on(event, onceProxy);
606
+ });
607
+ }
608
+ /** Emits an event on this instance - Needs `publicEmit` to be set to true in the constructor! */
609
+ emit(event, ...args) {
610
+ if (this.emitterOptions.publicEmit) {
611
+ this.events.emit(event, ...args);
612
+ return true;
613
+ }
614
+ return false;
615
+ }
616
+ /** Unsubscribes all event listeners */
617
+ unsubscribeAll() {
618
+ for (const unsub of this.eventUnsubscribes)
619
+ unsub();
620
+ this.eventUnsubscribes = [];
621
+ }
622
+ };
623
+
624
+ // lib/Dialog.ts
625
+ var defaultDialogCss = `.uu-no-select {
626
+ user-select: none;
627
+ }
628
+
629
+ .uu-dialog-bg {
630
+ --uu-dialog-bg: #333333;
631
+ --uu-dialog-bg-highlight: #252525;
632
+ --uu-scroll-indicator-bg: rgba(10, 10, 10, 0.7);
633
+ --uu-dialog-separator-color: #797979;
634
+ --uu-dialog-border-radius: 10px;
635
+ }
636
+
637
+ .uu-dialog-bg {
638
+ display: block;
639
+ position: fixed;
640
+ width: 100%;
641
+ height: 100%;
642
+ top: 0;
643
+ left: 0;
644
+ z-index: 5;
645
+ background-color: rgba(0, 0, 0, 0.6);
646
+ }
647
+
648
+ .uu-dialog {
649
+ --uu-calc-dialog-height: calc(min(100vh - 40px, var(--uu-dialog-height-max)));
650
+ position: absolute;
651
+ display: flex;
652
+ flex-direction: column;
653
+ width: calc(min(100% - 60px, var(--uu-dialog-width-max)));
654
+ border-radius: var(--uu-dialog-border-radius);
655
+ height: auto;
656
+ max-height: var(--uu-calc-dialog-height);
657
+ left: 50%;
658
+ top: 50%;
659
+ transform: translate(-50%, -50%);
660
+ z-index: 6;
661
+ color: #fff;
662
+ background-color: var(--uu-dialog-bg);
663
+ }
664
+
665
+ .uu-dialog.align-top {
666
+ top: 0;
667
+ transform: translate(-50%, 40px);
668
+ }
669
+
670
+ .uu-dialog.align-bottom {
671
+ top: 100%;
672
+ transform: translate(-50%, -100%);
673
+ }
674
+
675
+ .uu-dialog-body {
676
+ font-size: 1.5rem;
677
+ padding: 20px;
678
+ }
679
+
680
+ .uu-dialog-body.small {
681
+ padding: 15px;
682
+ }
683
+
684
+ #uu-dialog-opts {
685
+ display: flex;
686
+ flex-direction: column;
687
+ position: relative;
688
+ padding: 30px 0px;
689
+ overflow-y: auto;
690
+ }
691
+
692
+ .uu-dialog-header {
693
+ display: flex;
694
+ justify-content: space-between;
695
+ align-items: center;
696
+ margin-bottom: 6px;
697
+ padding: 15px 20px 15px 20px;
698
+ background-color: var(--uu-dialog-bg);
699
+ border: 2px solid var(--uu-dialog-separator-color);
700
+ border-style: none none solid none !important;
701
+ border-radius: var(--uu-dialog-border-radius) var(--uu-dialog-border-radius) 0px 0px;
702
+ }
703
+
704
+ .uu-dialog-header.small {
705
+ padding: 10px 15px;
706
+ border-style: none none solid none !important;
707
+ }
708
+
709
+ .uu-dialog-header-pad {
710
+ content: " ";
711
+ min-height: 32px;
712
+ }
713
+
714
+ .uu-dialog-header-pad.small {
715
+ min-height: 24px;
716
+ }
717
+
718
+ .uu-dialog-titlecont {
719
+ display: flex;
720
+ align-items: center;
721
+ }
722
+
723
+ .uu-dialog-titlecont-no-title {
724
+ display: flex;
725
+ justify-content: flex-end;
726
+ align-items: center;
727
+ }
728
+
729
+ .uu-dialog-title {
730
+ position: relative;
731
+ display: inline-block;
732
+ font-size: 22px;
733
+ }
734
+
735
+ .uu-dialog-close {
736
+ cursor: pointer;
737
+ }
738
+
739
+ .uu-dialog-header-img,
740
+ .uu-dialog-close
741
+ {
742
+ width: 32px;
743
+ height: 32px;
744
+ }
745
+
746
+ .uu-dialog-header-img.small,
747
+ .uu-dialog-close.small
748
+ {
749
+ width: 24px;
750
+ height: 24px;
751
+ }
752
+
753
+ .uu-dialog-footer {
754
+ font-size: 17px;
755
+ text-decoration: underline;
756
+ }
757
+
758
+ .uu-dialog-footer.hidden {
759
+ display: none;
760
+ }
761
+
762
+ .uu-dialog-footer-cont {
763
+ margin-top: 6px;
764
+ padding: 15px 20px;
765
+ background: var(--uu-dialog-bg);
766
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--uu-dialog-bg) 30%, var(--uu-dialog-bg) 100%);
767
+ border: 2px solid var(--uu-dialog-separator-color);
768
+ border-style: solid none none none !important;
769
+ border-radius: 0px 0px var(--uu-dialog-border-radius) var(--uu-dialog-border-radius);
770
+ }
771
+
772
+ .uu-dialog-footer-buttons-cont button:not(:last-of-type) {
773
+ margin-right: 15px;
774
+ }`;
775
+ exports.currentDialogId = null;
776
+ var openDialogs = [];
777
+ var defaultStrings = {
778
+ closeDialogTooltip: "Click to close the dialog"
779
+ };
780
+ var Dialog = class _Dialog extends NanoEmitter {
781
+ constructor(options) {
782
+ super();
783
+ /** Options passed to the dialog in the constructor */
784
+ __publicField(this, "options");
785
+ /** ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! */
786
+ __publicField(this, "id");
787
+ /** Strings used in the dialog (used for translations) */
788
+ __publicField(this, "strings");
789
+ __publicField(this, "dialogOpen", false);
790
+ __publicField(this, "dialogMounted", false);
791
+ const _a = options, { strings } = _a, opts = __objRest(_a, ["strings"]);
792
+ this.strings = __spreadValues(__spreadValues({}, defaultStrings), strings != null ? strings : {});
793
+ this.options = __spreadValues({
794
+ closeOnBgClick: true,
795
+ closeOnEscPress: true,
796
+ destroyOnClose: false,
797
+ unmountOnClose: true,
798
+ removeListenersOnDestroy: true,
799
+ smallHeader: false,
800
+ verticalAlign: "center",
801
+ dialogCss: defaultDialogCss
802
+ }, opts);
803
+ this.id = opts.id;
804
+ }
805
+ //#region public
806
+ /** Call after DOMContentLoaded to pre-render the dialog and invisibly mount it in the DOM */
807
+ mount() {
808
+ return __async(this, null, function* () {
809
+ if (this.dialogMounted)
810
+ return;
811
+ this.dialogMounted = true;
812
+ if (!document.querySelector("style.uu-dialog-css"))
813
+ addGlobalStyle(this.options.dialogCss).classList.add("uu-dialog-css");
814
+ const bgElem = document.createElement("div");
815
+ bgElem.id = `uu-${this.id}-dialog-bg`;
816
+ bgElem.classList.add("uu-dialog-bg");
817
+ if (this.options.closeOnBgClick)
818
+ bgElem.ariaLabel = bgElem.title = this.getString("closeDialogTooltip");
819
+ bgElem.style.setProperty("--uu-dialog-width-max", `${this.options.width}px`);
820
+ bgElem.style.setProperty("--uu-dialog-height-max", `${this.options.height}px`);
821
+ bgElem.style.visibility = "hidden";
822
+ bgElem.style.display = "none";
823
+ bgElem.inert = true;
824
+ bgElem.appendChild(yield this.getDialogContent());
825
+ document.body.appendChild(bgElem);
826
+ this.attachListeners(bgElem);
827
+ this.events.emit("render");
828
+ return bgElem;
829
+ });
830
+ }
831
+ /** Closes the dialog and clears all its contents (unmounts elements from the DOM) in preparation for a new rendering call */
832
+ unmount() {
833
+ var _a;
834
+ this.close();
835
+ this.dialogMounted = false;
836
+ const clearSelectors = [
837
+ `#uu-${this.id}-dialog-bg`,
838
+ `#uu-style-dialog-${this.id}`
839
+ ];
840
+ for (const sel of clearSelectors)
841
+ (_a = document.querySelector(sel)) == null ? void 0 : _a.remove();
842
+ this.events.emit("clear");
843
+ }
844
+ /** Clears the DOM of the dialog and then renders it again */
845
+ remount() {
846
+ return __async(this, null, function* () {
847
+ this.unmount();
848
+ yield this.mount();
849
+ });
850
+ }
851
+ /**
852
+ * Opens the dialog - also mounts it if it hasn't been mounted yet
853
+ * Prevents default action and immediate propagation of the passed event
854
+ */
855
+ open(e) {
856
+ return __async(this, null, function* () {
857
+ var _a;
858
+ e == null ? void 0 : e.preventDefault();
859
+ e == null ? void 0 : e.stopImmediatePropagation();
860
+ if (this.isOpen())
861
+ return;
862
+ this.dialogOpen = true;
863
+ if (openDialogs.includes(this.id))
864
+ throw new Error(`A dialog with the same ID of '${this.id}' already exists and is open!`);
865
+ if (!this.isMounted())
866
+ yield this.mount();
867
+ const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
868
+ if (!dialogBg)
869
+ return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
870
+ dialogBg.style.visibility = "visible";
871
+ dialogBg.style.display = "block";
872
+ dialogBg.inert = false;
873
+ exports.currentDialogId = this.id;
874
+ openDialogs.unshift(this.id);
875
+ for (const dialogId of openDialogs)
876
+ if (dialogId !== this.id)
877
+ (_a = document.querySelector(`#uu-${dialogId}-dialog-bg`)) == null ? void 0 : _a.setAttribute("inert", "true");
878
+ document.body.classList.remove("uu-no-select");
879
+ document.body.setAttribute("inert", "true");
880
+ this.events.emit("open");
881
+ return dialogBg;
882
+ });
883
+ }
884
+ /** Closes the dialog - prevents default action and immediate propagation of the passed event */
885
+ close(e) {
886
+ var _a, _b;
887
+ e == null ? void 0 : e.preventDefault();
888
+ e == null ? void 0 : e.stopImmediatePropagation();
889
+ if (!this.isOpen())
890
+ return;
891
+ this.dialogOpen = false;
892
+ const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
893
+ if (!dialogBg)
894
+ return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
895
+ dialogBg.style.visibility = "hidden";
896
+ dialogBg.style.display = "none";
897
+ dialogBg.inert = true;
898
+ openDialogs.splice(openDialogs.indexOf(this.id), 1);
899
+ exports.currentDialogId = (_a = openDialogs[0]) != null ? _a : null;
900
+ if (exports.currentDialogId)
901
+ (_b = document.querySelector(`#uu-${exports.currentDialogId}-dialog-bg`)) == null ? void 0 : _b.removeAttribute("inert");
902
+ if (openDialogs.length === 0) {
903
+ document.body.classList.add("uu-no-select");
904
+ document.body.removeAttribute("inert");
905
+ }
906
+ this.events.emit("close");
907
+ if (this.options.destroyOnClose)
908
+ this.destroy();
909
+ else if (this.options.unmountOnClose)
910
+ this.unmount();
911
+ }
912
+ /** Returns true if the dialog is currently open */
913
+ isOpen() {
914
+ return this.dialogOpen;
915
+ }
916
+ /** Returns true if the dialog is currently mounted */
917
+ isMounted() {
918
+ return this.dialogMounted;
919
+ }
920
+ /** Clears the DOM of the dialog and removes all event listeners */
921
+ destroy() {
922
+ this.unmount();
923
+ this.events.emit("destroy");
924
+ this.options.removeListenersOnDestroy && this.unsubscribeAll();
925
+ }
926
+ //#region static
927
+ /** Returns the ID of the top-most dialog (the dialog that has been opened last) */
928
+ static getCurrentDialogId() {
929
+ return exports.currentDialogId;
930
+ }
931
+ /** Returns the IDs of all currently open dialogs, top-most first */
932
+ static getOpenDialogs() {
933
+ return openDialogs;
934
+ }
935
+ //#region protected
936
+ getString(key) {
937
+ var _a;
938
+ return (_a = this.strings[key]) != null ? _a : defaultStrings[key];
939
+ }
940
+ /** Called once to attach all generic event listeners */
941
+ attachListeners(bgElem) {
942
+ if (this.options.closeOnBgClick) {
943
+ bgElem.addEventListener("click", (e) => {
944
+ var _a;
945
+ if (this.isOpen() && ((_a = e.target) == null ? void 0 : _a.id) === `uu-${this.id}-dialog-bg`)
946
+ this.close(e);
947
+ });
948
+ }
949
+ if (this.options.closeOnEscPress) {
950
+ document.body.addEventListener("keydown", (e) => {
951
+ if (e.key === "Escape" && this.isOpen() && _Dialog.getCurrentDialogId() === this.id)
952
+ this.close(e);
953
+ });
954
+ }
955
+ }
956
+ //#region protected
957
+ /**
958
+ * Adds generic, accessible interaction listeners to the passed element.
959
+ * All listeners have the default behavior prevented and stop propagation (for keyboard events only as long as the captured key is valid).
960
+ * @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
961
+ */
962
+ onInteraction(elem, listener, listenerOptions) {
963
+ const _a = listenerOptions != null ? listenerOptions : {}, { preventDefault = true, stopPropagation = true } = _a, listenerOpts = __objRest(_a, ["preventDefault", "stopPropagation"]);
964
+ const interactionKeys = ["Enter", " ", "Space"];
965
+ const proxListener = (e) => {
966
+ if (e instanceof KeyboardEvent) {
967
+ if (interactionKeys.includes(e.key)) {
968
+ preventDefault && e.preventDefault();
969
+ stopPropagation && e.stopPropagation();
970
+ } else
971
+ return;
972
+ } else if (e instanceof MouseEvent) {
973
+ preventDefault && e.preventDefault();
974
+ stopPropagation && e.stopPropagation();
975
+ }
976
+ (listenerOpts == null ? void 0 : listenerOpts.once) && e.type === "keydown" && elem.removeEventListener("click", proxListener, listenerOpts);
977
+ (listenerOpts == null ? void 0 : listenerOpts.once) && e.type === "click" && elem.removeEventListener("keydown", proxListener, listenerOpts);
978
+ listener(e);
979
+ };
980
+ elem.addEventListener("click", proxListener, listenerOpts);
981
+ elem.addEventListener("keydown", proxListener, listenerOpts);
982
+ }
983
+ /** Returns the dialog content element and all its children */
984
+ getDialogContent() {
985
+ return __async(this, null, function* () {
986
+ var _a, _b, _c, _d;
987
+ const header = (_b = (_a = this.options).renderHeader) == null ? void 0 : _b.call(_a);
988
+ const footer = (_d = (_c = this.options).renderFooter) == null ? void 0 : _d.call(_c);
989
+ const dialogWrapperEl = document.createElement("div");
990
+ dialogWrapperEl.id = `uu-${this.id}-dialog`;
991
+ dialogWrapperEl.classList.add("uu-dialog");
992
+ dialogWrapperEl.ariaLabel = dialogWrapperEl.title = "";
993
+ dialogWrapperEl.role = "dialog";
994
+ dialogWrapperEl.setAttribute("aria-labelledby", `uu-${this.id}-dialog-title`);
995
+ dialogWrapperEl.setAttribute("aria-describedby", `uu-${this.id}-dialog-body`);
996
+ if (this.options.verticalAlign !== "center")
997
+ dialogWrapperEl.classList.add(`align-${this.options.verticalAlign}`);
998
+ const headerWrapperEl = document.createElement("div");
999
+ headerWrapperEl.classList.add("uu-dialog-header");
1000
+ this.options.small && headerWrapperEl.classList.add("small");
1001
+ if (header) {
1002
+ const headerTitleWrapperEl = document.createElement("div");
1003
+ headerTitleWrapperEl.id = `uu-${this.id}-dialog-title`;
1004
+ headerTitleWrapperEl.classList.add("uu-dialog-title-wrapper");
1005
+ headerTitleWrapperEl.role = "heading";
1006
+ headerTitleWrapperEl.ariaLevel = "1";
1007
+ headerTitleWrapperEl.appendChild(header instanceof Promise ? yield header : header);
1008
+ headerWrapperEl.appendChild(headerTitleWrapperEl);
1009
+ } else {
1010
+ const padEl = document.createElement("div");
1011
+ padEl.classList.add("uu-dialog-header-pad", this.options.small ? "small" : "");
1012
+ headerWrapperEl.appendChild(padEl);
1013
+ }
1014
+ if (this.options.renderCloseBtn) {
1015
+ const closeBtnEl = yield this.options.renderCloseBtn();
1016
+ closeBtnEl.classList.add("uu-dialog-close");
1017
+ this.options.small && closeBtnEl.classList.add("small");
1018
+ closeBtnEl.tabIndex = 0;
1019
+ if (closeBtnEl.hasAttribute("alt"))
1020
+ closeBtnEl.setAttribute("alt", this.getString("closeDialogTooltip"));
1021
+ closeBtnEl.title = closeBtnEl.ariaLabel = this.getString("closeDialogTooltip");
1022
+ this.onInteraction(closeBtnEl, () => this.close());
1023
+ headerWrapperEl.appendChild(closeBtnEl);
1024
+ }
1025
+ dialogWrapperEl.appendChild(headerWrapperEl);
1026
+ const dialogBodyElem = document.createElement("div");
1027
+ dialogBodyElem.id = `uu-${this.id}-dialog-body`;
1028
+ dialogBodyElem.classList.add("uu-dialog-body");
1029
+ this.options.small && dialogBodyElem.classList.add("small");
1030
+ const body = this.options.renderBody();
1031
+ dialogBodyElem.appendChild(body instanceof Promise ? yield body : body);
1032
+ dialogWrapperEl.appendChild(dialogBodyElem);
1033
+ if (footer) {
1034
+ const footerWrapper = document.createElement("div");
1035
+ footerWrapper.classList.add("uu-dialog-footer-cont");
1036
+ dialogWrapperEl.appendChild(footerWrapper);
1037
+ footerWrapper.appendChild(footer instanceof Promise ? yield footer : footer);
1038
+ }
1039
+ return dialogWrapperEl;
1040
+ });
1041
+ }
1042
+ };
1043
+
555
1044
  // lib/misc.ts
556
1045
  function autoPlural(word, num) {
557
1046
  if (Array.isArray(num) || num instanceof NodeList)
@@ -865,6 +1354,8 @@ Has: ${checksum}`);
865
1354
 
866
1355
  exports.DataStore = DataStore;
867
1356
  exports.DataStoreSerializer = DataStoreSerializer;
1357
+ exports.Dialog = Dialog;
1358
+ exports.NanoEmitter = NanoEmitter;
868
1359
  exports.SelectorObserver = SelectorObserver;
869
1360
  exports.addGlobalStyle = addGlobalStyle;
870
1361
  exports.addParent = addParent;
@@ -874,6 +1365,8 @@ Has: ${checksum}`);
874
1365
  exports.computeHash = computeHash;
875
1366
  exports.debounce = debounce;
876
1367
  exports.decompress = decompress;
1368
+ exports.defaultDialogCss = defaultDialogCss;
1369
+ exports.defaultStrings = defaultStrings;
877
1370
  exports.fetchAdvanced = fetchAdvanced;
878
1371
  exports.getSiblingsFrame = getSiblingsFrame;
879
1372
  exports.getUnsafeWindow = getUnsafeWindow;
@@ -883,6 +1376,7 @@ Has: ${checksum}`);
883
1376
  exports.isScrollable = isScrollable;
884
1377
  exports.mapRange = mapRange;
885
1378
  exports.observeElementProp = observeElementProp;
1379
+ exports.openDialogs = openDialogs;
886
1380
  exports.openInNewTab = openInNewTab;
887
1381
  exports.pauseFor = pauseFor;
888
1382
  exports.preloadImages = preloadImages;