@schukai/monster 4.127.1 → 4.128.1
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 +20 -1
- package/package.json +1 -1
- package/source/components/datatable/datatable.mjs +239 -46
- package/source/components/datatable/style/datatable.pcss +5 -0
- package/source/components/datatable/stylesheet/datatable.mjs +1 -1
- package/source/components/form/message-state-button.mjs +23 -3
- package/source/components/layout/popper.mjs +37 -6
- package/source/dom/constants.mjs +6 -0
- package/source/dom/customelement.mjs +15 -0
- package/source/dom/events.mjs +6 -1
- package/source/dom/updater.mjs +475 -14
- package/test/cases/components/datatable/drag-scroll.mjs +258 -0
- package/test/cases/components/form/message-state-button.mjs +85 -0
- package/test/cases/dom/customelement.mjs +78 -8
- package/test/cases/dom/updater.mjs +848 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [Unreleased]
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **updater:** add explicit disposal semantics for observer, queue, and event-processing teardown
|
|
9
|
+
- **updater:** dispose linked stateful subtrees before destructive replace/remove operations
|
|
10
|
+
- **updater:** add `data-monster-patch` as a lifecycle-safe alternative to destructive replace rendering
|
|
11
|
+
- **updater:** extend `data-monster-patch` with `DocumentFragment` and unkeyed array support
|
|
12
|
+
- **updater:** add initial `data-monster-patch-key` support for keyed array reorder and removal
|
|
13
|
+
- **updater:** add `data-monster-patch-render` for keyed object arrays with explicit single-node item rendering
|
|
14
|
+
- **message-state-button:** render `message.content` through `data-monster-patch` to preserve rich and stateful message content
|
|
15
|
+
- **customelement:** skip mutation-observer updater reruns for disconnected or disposed instances
|
|
16
|
+
- **message-state-button:** clear auto-hide timers on disconnect and ignore delayed hides after removal
|
|
17
|
+
- **popper:** guard show/hide/update flows against disconnected hosts and missing internal elements
|
|
18
|
+
|
|
19
|
+
### Changes
|
|
20
|
+
|
|
21
|
+
- document lifecycle ownership rules for Updater-driven and stateful custom element implementations
|
|
22
|
+
|
|
23
|
+
|
|
4
24
|
|
|
5
25
|
## [4.127.1] - 2026-03-16
|
|
6
26
|
|
|
@@ -4658,4 +4678,3 @@
|
|
|
4658
4678
|
## 1.8.0 - 2021-08-15
|
|
4659
4679
|
|
|
4660
4680
|
- Initial release
|
|
4661
|
-
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6","@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.128.1"}
|
|
@@ -117,6 +117,12 @@ const columnBarElementSymbol = Symbol("columnBarElement");
|
|
|
117
117
|
*/
|
|
118
118
|
const copyAllElementSymbol = Symbol("copyAllElement");
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* @private
|
|
122
|
+
* @type {symbol}
|
|
123
|
+
*/
|
|
124
|
+
const tableScrollElementSymbol = Symbol("tableScrollElement");
|
|
125
|
+
|
|
120
126
|
/**
|
|
121
127
|
* @private
|
|
122
128
|
* @type {symbol}
|
|
@@ -794,6 +800,7 @@ function getCellValueForCopy(cell) {
|
|
|
794
800
|
*/
|
|
795
801
|
function initEventHandler() {
|
|
796
802
|
const self = this;
|
|
803
|
+
const tableScrollElement = this[tableScrollElementSymbol];
|
|
797
804
|
|
|
798
805
|
// --- Column resizing state ---
|
|
799
806
|
let isResizing = false;
|
|
@@ -801,6 +808,166 @@ function initEventHandler() {
|
|
|
801
808
|
let startX = 0;
|
|
802
809
|
let startWidth = 0;
|
|
803
810
|
|
|
811
|
+
// --- Table drag scrolling state ---
|
|
812
|
+
let isTableDragging = false;
|
|
813
|
+
let tableDragMoved = false;
|
|
814
|
+
let tableDragPointerId = null;
|
|
815
|
+
let tableDragStartX = 0;
|
|
816
|
+
let tableDragStartY = 0;
|
|
817
|
+
let tableDragStartScrollLeft = 0;
|
|
818
|
+
let suppressClickUntil = 0;
|
|
819
|
+
|
|
820
|
+
const isInteractiveDragTarget = (event) => {
|
|
821
|
+
if (
|
|
822
|
+
findTargetElementFromEvent(event, "data-monster-role", "resize-handle")
|
|
823
|
+
) {
|
|
824
|
+
return true;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const path = event.composedPath?.();
|
|
828
|
+
if (!isArray(path)) {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
for (const node of path) {
|
|
833
|
+
if (!(node instanceof HTMLElement)) {
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (node === tableScrollElement) {
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (
|
|
842
|
+
node.matches(
|
|
843
|
+
[
|
|
844
|
+
"button",
|
|
845
|
+
"a",
|
|
846
|
+
"input",
|
|
847
|
+
"select",
|
|
848
|
+
"textarea",
|
|
849
|
+
"label",
|
|
850
|
+
"summary",
|
|
851
|
+
"[contenteditable]",
|
|
852
|
+
"[contenteditable='true']",
|
|
853
|
+
"[draggable='true']",
|
|
854
|
+
"[role='button']",
|
|
855
|
+
"[role='link']",
|
|
856
|
+
"[role='textbox']",
|
|
857
|
+
"[role='checkbox']",
|
|
858
|
+
"[role='switch']",
|
|
859
|
+
"[role='menuitem']",
|
|
860
|
+
].join(","),
|
|
861
|
+
)
|
|
862
|
+
) {
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return false;
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const stopTableDragging = () => {
|
|
871
|
+
if (tableScrollElement instanceof HTMLElement) {
|
|
872
|
+
tableScrollElement.classList.remove("is-dragging");
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
getWindow().removeEventListener("pointermove", onTablePointerMove);
|
|
876
|
+
getWindow().removeEventListener("pointerup", onTablePointerUp);
|
|
877
|
+
getWindow().removeEventListener("pointercancel", onTablePointerUp);
|
|
878
|
+
|
|
879
|
+
if (tableDragPointerId !== null) {
|
|
880
|
+
tableScrollElement?.releasePointerCapture?.(tableDragPointerId);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
isTableDragging = false;
|
|
884
|
+
tableDragMoved = false;
|
|
885
|
+
tableDragPointerId = null;
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
const onTablePointerDown = (event) => {
|
|
889
|
+
if (!(tableScrollElement instanceof HTMLElement)) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (
|
|
894
|
+
(event.pointerType === "mouse" && event.button !== 0) ||
|
|
895
|
+
event.isPrimary === false
|
|
896
|
+
) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (tableScrollElement.scrollWidth <= tableScrollElement.clientWidth) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (isInteractiveDragTarget(event)) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
isTableDragging = true;
|
|
909
|
+
tableDragMoved = false;
|
|
910
|
+
tableDragPointerId = event.pointerId;
|
|
911
|
+
tableDragStartX = event.clientX;
|
|
912
|
+
tableDragStartY = event.clientY;
|
|
913
|
+
tableDragStartScrollLeft = tableScrollElement.scrollLeft;
|
|
914
|
+
|
|
915
|
+
tableScrollElement.setPointerCapture?.(event.pointerId);
|
|
916
|
+
getWindow().addEventListener("pointermove", onTablePointerMove);
|
|
917
|
+
getWindow().addEventListener("pointerup", onTablePointerUp);
|
|
918
|
+
getWindow().addEventListener("pointercancel", onTablePointerUp);
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
const onTablePointerMove = (event) => {
|
|
922
|
+
if (
|
|
923
|
+
!isTableDragging ||
|
|
924
|
+
tableDragPointerId === null ||
|
|
925
|
+
event.pointerId !== tableDragPointerId ||
|
|
926
|
+
!(tableScrollElement instanceof HTMLElement)
|
|
927
|
+
) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const deltaX = event.clientX - tableDragStartX;
|
|
932
|
+
const deltaY = event.clientY - tableDragStartY;
|
|
933
|
+
|
|
934
|
+
if (!tableDragMoved) {
|
|
935
|
+
if (Math.abs(deltaX) < 8 || Math.abs(deltaX) <= Math.abs(deltaY)) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
tableDragMoved = true;
|
|
940
|
+
tableScrollElement.classList.add("is-dragging");
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
event.preventDefault();
|
|
944
|
+
tableScrollElement.scrollLeft = tableDragStartScrollLeft - deltaX;
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
const onTablePointerUp = (event) => {
|
|
948
|
+
if (tableDragPointerId === null || event.pointerId !== tableDragPointerId) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (tableDragMoved) {
|
|
953
|
+
suppressClickUntil = Date.now() + 250;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
stopTableDragging();
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
const onTableClickCapture = (event) => {
|
|
960
|
+
if (suppressClickUntil < Date.now()) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
event.preventDefault();
|
|
965
|
+
event.stopPropagation();
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
tableScrollElement?.addEventListener("pointerdown", onTablePointerDown);
|
|
969
|
+
tableScrollElement?.addEventListener("click", onTableClickCapture, true);
|
|
970
|
+
|
|
804
971
|
// --- Pointer-based resize handlers (robust for mouse/touch/stylus) ---
|
|
805
972
|
const onPointerDown = (event) => {
|
|
806
973
|
// Important: Call helper with (attrName, value) – not with CSS selector
|
|
@@ -879,6 +1046,58 @@ function initEventHandler() {
|
|
|
879
1046
|
const delimiterChar = this.getOption("copy.delimiter");
|
|
880
1047
|
const rowBreak = this.getOption("copy.rowBreak");
|
|
881
1048
|
|
|
1049
|
+
const writeTextToClipboard = (text) => {
|
|
1050
|
+
if (getWindow().navigator.clipboard && text) {
|
|
1051
|
+
getWindow()
|
|
1052
|
+
.navigator.clipboard.writeText(text)
|
|
1053
|
+
.then(
|
|
1054
|
+
() => {},
|
|
1055
|
+
() => {},
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
const getCopyTextForRow = (headCell) => {
|
|
1061
|
+
const index = headCell.getAttribute("data-monster-insert-reference");
|
|
1062
|
+
if (!index) {
|
|
1063
|
+
return "";
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const cols = self.getGridElements(
|
|
1067
|
+
`[data-monster-insert-reference="${index}"]`,
|
|
1068
|
+
);
|
|
1069
|
+
const colTexts = [];
|
|
1070
|
+
|
|
1071
|
+
for (let i = 0; i < cols.length; i++) {
|
|
1072
|
+
const col = cols[i];
|
|
1073
|
+
|
|
1074
|
+
if (
|
|
1075
|
+
col.querySelector("monster-button-bar") ||
|
|
1076
|
+
col.querySelector("monster-button")
|
|
1077
|
+
) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const cellValue = getCellValueForCopy(col);
|
|
1082
|
+
if (cellValue) {
|
|
1083
|
+
colTexts.push(quoteOpenChar + cellValue + quoteCloseChar);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return colTexts.join(delimiterChar);
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
const getCopyTextForCell = (headCell) => {
|
|
1091
|
+
if (
|
|
1092
|
+
headCell.querySelector("monster-button-bar") ||
|
|
1093
|
+
headCell.querySelector("monster-button")
|
|
1094
|
+
) {
|
|
1095
|
+
return "";
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return getCellValueForCopy(headCell);
|
|
1099
|
+
};
|
|
1100
|
+
|
|
882
1101
|
// --- Column-Bar -> Header visibility & persistence ---
|
|
883
1102
|
if (self[columnBarElementSymbol]) {
|
|
884
1103
|
self[columnBarElementSymbol].attachObserver(
|
|
@@ -917,60 +1136,30 @@ function initEventHandler() {
|
|
|
917
1136
|
}
|
|
918
1137
|
});
|
|
919
1138
|
|
|
920
|
-
// ---
|
|
921
|
-
const
|
|
1139
|
+
// --- Copy interactions ---
|
|
1140
|
+
const eventHandlerShiftClickCopyRowToClipboard = (event) => {
|
|
922
1141
|
const headCell = findTargetElementFromEvent(event, "data-monster-head");
|
|
923
|
-
if (!headCell)
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if (event.shiftKey) {
|
|
928
|
-
const index = headCell.getAttribute("data-monster-insert-reference");
|
|
929
|
-
if (index) {
|
|
930
|
-
const cols = self.getGridElements(
|
|
931
|
-
`[data-monster-insert-reference="${index}"]`,
|
|
932
|
-
);
|
|
933
|
-
|
|
934
|
-
const colTexts = [];
|
|
935
|
-
for (let i = 0; i < cols.length; i++) {
|
|
936
|
-
const col = cols[i];
|
|
937
|
-
|
|
938
|
-
if (
|
|
939
|
-
col.querySelector("monster-button-bar") ||
|
|
940
|
-
col.querySelector("monster-button")
|
|
941
|
-
) {
|
|
942
|
-
continue;
|
|
943
|
-
}
|
|
1142
|
+
if (!headCell || !event.shiftKey) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
944
1145
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
colTexts.push(quoteOpenChar + cellValue + quoteCloseChar);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
1146
|
+
writeTextToClipboard(getCopyTextForRow(headCell));
|
|
1147
|
+
};
|
|
950
1148
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
headCell.querySelector("monster-button-bar") ||
|
|
956
|
-
headCell.querySelector("monster-button")
|
|
957
|
-
) {
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
text = getCellValueForCopy(headCell);
|
|
1149
|
+
const eventHandlerDoubleClickCopyToClipboard = (event) => {
|
|
1150
|
+
const headCell = findTargetElementFromEvent(event, "data-monster-head");
|
|
1151
|
+
if (!headCell || event.shiftKey) {
|
|
1152
|
+
return;
|
|
961
1153
|
}
|
|
962
1154
|
|
|
963
|
-
|
|
964
|
-
getWindow()
|
|
965
|
-
.navigator.clipboard.writeText(text)
|
|
966
|
-
.then(
|
|
967
|
-
() => {},
|
|
968
|
-
() => {},
|
|
969
|
-
);
|
|
970
|
-
}
|
|
1155
|
+
writeTextToClipboard(getCopyTextForCell(headCell));
|
|
971
1156
|
};
|
|
972
1157
|
|
|
973
1158
|
if (self.getOption("features.doubleClickCopyToClipboard")) {
|
|
1159
|
+
self[gridElementSymbol].addEventListener(
|
|
1160
|
+
"click",
|
|
1161
|
+
eventHandlerShiftClickCopyRowToClipboard,
|
|
1162
|
+
);
|
|
974
1163
|
self[gridElementSymbol].addEventListener(
|
|
975
1164
|
"dblclick",
|
|
976
1165
|
eventHandlerDoubleClickCopyToClipboard,
|
|
@@ -1537,6 +1726,10 @@ function initControlReferences() {
|
|
|
1537
1726
|
"[data-monster-role=copy-all]",
|
|
1538
1727
|
);
|
|
1539
1728
|
|
|
1729
|
+
this[tableScrollElementSymbol] = this.shadowRoot.querySelector(
|
|
1730
|
+
"[data-monster-role=table-scroll]",
|
|
1731
|
+
);
|
|
1732
|
+
|
|
1540
1733
|
return this;
|
|
1541
1734
|
}
|
|
1542
1735
|
|