@themarioga/grid-editor 9.0.0-beta.2 → 9.0.0-beta.3

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.
@@ -0,0 +1,365 @@
1
+ (() => {
2
+ // src/js/register.js
3
+ var GridEditor = window.GridEditor;
4
+ if (!GridEditor || typeof GridEditor.get !== "function") {
5
+ throw new Error("grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, its locales and grideditor.jquery.js");
6
+ }
7
+
8
+ // src/js/dom.js
9
+ function one(root, selector) {
10
+ return root ? root.querySelector(selector) : null;
11
+ }
12
+ function children(node, selector) {
13
+ if (!node) {
14
+ return [];
15
+ }
16
+ return Array.prototype.filter.call(node.children, function(each) {
17
+ return !selector || each.matches(selector);
18
+ });
19
+ }
20
+ function is(node, selector) {
21
+ return !!node && node.nodeType === 1 && node.matches(selector);
22
+ }
23
+ function toggleClass(node, names, state) {
24
+ split(names).forEach(function(name) {
25
+ if (state === void 0) {
26
+ node.classList.toggle(name);
27
+ } else {
28
+ node.classList.toggle(name, !!state);
29
+ }
30
+ });
31
+ return node;
32
+ }
33
+ function hasClass(node, name) {
34
+ return !!node && node.nodeType === 1 && node.classList.contains(name);
35
+ }
36
+ function split(names) {
37
+ return String(names || "").split(/\s+/).filter(Boolean);
38
+ }
39
+
40
+ // src/js/plugins/grideditor.history.js
41
+ Object.assign(GridEditor.locales.en, {
42
+ "history.undo": "Undo",
43
+ "history.redo": "Redo"
44
+ });
45
+ var DEFAULT_MAX = 50;
46
+ var DEFAULT_DELAY = 500;
47
+ var FURNITURE = ".ge-tools-drawer, .ge-drop-marker";
48
+ var BUSY = ".ge-drag-chosen, .ge-drag-placeholder, .ge-drag-helper, .ge-resizing";
49
+ var listeningEditors = [];
50
+ var lastEditor = null;
51
+ function track(e) {
52
+ lastEditor = listeningEditors.filter(function(ge) {
53
+ return ge.contains(e.target);
54
+ })[0] || null;
55
+ }
56
+ document.addEventListener("pointerdown", track, true);
57
+ document.addEventListener("focusin", track, true);
58
+ function shortcutKey(e) {
59
+ var key = (e.key || "").toLowerCase();
60
+ if (!/^[a-z]$/.test(key)) {
61
+ key = { KeyZ: "z", KeyY: "y" }[e.code] || "";
62
+ }
63
+ return key === "z" || key === "y" ? key : null;
64
+ }
65
+ function typing() {
66
+ var focus = document.activeElement;
67
+ return !!focus && (is(focus, "input, textarea, select") || focus.isContentEditable);
68
+ }
69
+ function contentOf(parent) {
70
+ return children(parent).filter(function(child) {
71
+ return !is(child, FURNITURE);
72
+ });
73
+ }
74
+ GridEditor.features.history = function(ge) {
75
+ var options = optionsFrom(ge.settings.history || {});
76
+ var stack = [];
77
+ var index = -1;
78
+ var shown = { canUndo: false, canRedo: false };
79
+ var timer = null;
80
+ var observer = null;
81
+ var observing = false;
82
+ var initiated = false;
83
+ var started = false;
84
+ var sourceOpen = false;
85
+ var destroyed = false;
86
+ function optionsFrom(given) {
87
+ var result = {
88
+ shortcuts: given.shortcuts === void 0 ? true : given.shortcuts,
89
+ buttons: given.buttons === void 0 ? true : given.buttons,
90
+ max: given.max === void 0 ? DEFAULT_MAX : given.max,
91
+ delay: given.delay === void 0 ? DEFAULT_DELAY : given.delay
92
+ };
93
+ ["shortcuts", "buttons"].forEach(function(name) {
94
+ if (typeof result[name] !== "boolean") {
95
+ ge.warn("history." + name + " is true or false: true is used");
96
+ result[name] = true;
97
+ }
98
+ });
99
+ if (!Number.isInteger(result.max) || result.max < 1) {
100
+ ge.warn("history.max must be a whole number of steps, 1 or more: " + DEFAULT_MAX + " is used");
101
+ result.max = DEFAULT_MAX;
102
+ }
103
+ if (!Number.isInteger(result.delay) || result.delay < 0) {
104
+ ge.warn("history.delay must be a whole number of milliseconds, 0 or more: " + DEFAULT_DELAY + " is used");
105
+ result.delay = DEFAULT_DELAY;
106
+ }
107
+ return result;
108
+ }
109
+ function observe() {
110
+ observer.observe(ge.canvas, { subtree: true, childList: true, attributes: true, characterData: true });
111
+ observing = true;
112
+ }
113
+ function stopObserving() {
114
+ if (observer) {
115
+ observer.disconnect();
116
+ }
117
+ observing = false;
118
+ }
119
+ function read() {
120
+ var watching = observing;
121
+ if (watching) {
122
+ stopObserving();
123
+ }
124
+ var html = ge.snapshotHtml();
125
+ if (watching) {
126
+ observe();
127
+ }
128
+ return html;
129
+ }
130
+ function editing() {
131
+ return hasClass(ge.canvas, "ge-editing");
132
+ }
133
+ function busy() {
134
+ return hasClass(ge.canvas, "ge-dropping") || !!one(ge.canvas, BUSY);
135
+ }
136
+ function schedule() {
137
+ window.clearTimeout(timer);
138
+ timer = window.setTimeout(commit, options.delay);
139
+ }
140
+ function pending() {
141
+ return timer !== null && read() !== stack[index];
142
+ }
143
+ function commit() {
144
+ window.clearTimeout(timer);
145
+ timer = null;
146
+ if (!started || destroyed || sourceOpen) {
147
+ return;
148
+ }
149
+ if (busy()) {
150
+ schedule();
151
+ return;
152
+ }
153
+ var html = read();
154
+ if (html === stack[index]) {
155
+ return;
156
+ }
157
+ stack.splice(index + 1);
158
+ stack.push(html);
159
+ if (stack.length > options.max + 1) {
160
+ stack.shift();
161
+ }
162
+ index = stack.length - 1;
163
+ notify();
164
+ }
165
+ function notify() {
166
+ var now = { canUndo: index > 0, canRedo: index < stack.length - 1 };
167
+ var changed = now.canUndo !== shown.canUndo || now.canRedo !== shown.canRedo;
168
+ shown = now;
169
+ paint();
170
+ if (changed) {
171
+ ge.emit("history-change", { canvas: ge.canvas, canUndo: now.canUndo, canRedo: now.canRedo });
172
+ }
173
+ }
174
+ function paint() {
175
+ ge.toolbarItems("history").forEach(function(button) {
176
+ var off = button.getAttribute("data-ge-item") === "0" ? !shown.canUndo : !shown.canRedo;
177
+ toggleClass(button, "disabled", off);
178
+ if (off) {
179
+ button.setAttribute("aria-disabled", "true");
180
+ } else {
181
+ button.removeAttribute("aria-disabled");
182
+ }
183
+ });
184
+ }
185
+ function pathOf(node) {
186
+ if (!node || node === ge.canvas || !ge.canvas.contains(node)) {
187
+ return null;
188
+ }
189
+ var path = [];
190
+ for (var at = node; at !== ge.canvas; at = at.parentElement) {
191
+ path.unshift(contentOf(at.parentElement).indexOf(at));
192
+ }
193
+ return path;
194
+ }
195
+ function resolve(path) {
196
+ if (!path) {
197
+ return null;
198
+ }
199
+ var node = ge.canvas;
200
+ for (var i = 0; i < path.length && node; i++) {
201
+ node = contentOf(node)[path[i]];
202
+ }
203
+ return node || null;
204
+ }
205
+ function go(step, source) {
206
+ if (!started || destroyed || sourceOpen || !editing() || busy()) {
207
+ return false;
208
+ }
209
+ commit();
210
+ var to = index + step;
211
+ if (to < 0 || to >= stack.length) {
212
+ return false;
213
+ }
214
+ var selected = pathOf(ge.getSelected());
215
+ var target = pathOf(ge.getActiveTarget());
216
+ stopObserving();
217
+ index = to;
218
+ ge.setHtml(stack[index]);
219
+ stack[index] = ge.snapshotHtml();
220
+ var node = resolve(selected);
221
+ if (node) {
222
+ ge.setSelected(node);
223
+ }
224
+ node = resolve(target);
225
+ if (node) {
226
+ ge.setActiveTarget(node);
227
+ }
228
+ observe();
229
+ notify();
230
+ ge.emit(step < 0 ? "after-undo" : "after-redo", { canvas: ge.canvas, html: stack[index], source });
231
+ return true;
232
+ }
233
+ function onKeydown(e) {
234
+ if (lastEditor !== ge || e.altKey || !(e.ctrlKey || e.metaKey)) {
235
+ return;
236
+ }
237
+ var key = shortcutKey(e);
238
+ if (!key || key === "y" && e.shiftKey) {
239
+ return;
240
+ }
241
+ if (!editing() || typing() || ge.panelOpen()) {
242
+ return;
243
+ }
244
+ if (go(key === "y" || e.shiftKey ? 1 : -1, "shortcut")) {
245
+ e.preventDefault();
246
+ }
247
+ }
248
+ function listenForShortcuts(on) {
249
+ var at = listeningEditors.indexOf(ge);
250
+ if (on && at === -1) {
251
+ listeningEditors.push(ge);
252
+ document.addEventListener("keydown", onKeydown);
253
+ } else if (!on && at !== -1) {
254
+ listeningEditors.splice(at, 1);
255
+ document.removeEventListener("keydown", onKeydown);
256
+ }
257
+ }
258
+ function start() {
259
+ if (destroyed) {
260
+ return;
261
+ }
262
+ stack = [read()];
263
+ index = 0;
264
+ observer = new MutationObserver(schedule);
265
+ observe();
266
+ started = true;
267
+ notify();
268
+ }
269
+ return {
270
+ toolbar: function() {
271
+ if (!options.buttons) {
272
+ return [];
273
+ }
274
+ return [
275
+ {
276
+ labelKey: "history.undo",
277
+ iconClass: "bi bi-arrow-counterclockwise",
278
+ className: "ge-history-undo",
279
+ align: "end",
280
+ disabled: function() {
281
+ return !shown.canUndo;
282
+ },
283
+ action: function() {
284
+ go(-1, "tool");
285
+ }
286
+ },
287
+ {
288
+ labelKey: "history.redo",
289
+ iconClass: "bi bi-arrow-clockwise",
290
+ className: "ge-history-redo",
291
+ align: "end",
292
+ disabled: function() {
293
+ return !shown.canRedo;
294
+ },
295
+ action: function() {
296
+ go(1, "tool");
297
+ }
298
+ }
299
+ ];
300
+ },
301
+ onInit: function() {
302
+ if (options.shortcuts) {
303
+ listenForShortcuts(true);
304
+ }
305
+ if (initiated) {
306
+ return;
307
+ }
308
+ initiated = true;
309
+ Promise.resolve().then(start);
310
+ },
311
+ // A canvas out of editing leaves the keys to the page: it is read again when it is back
312
+ onDeinit: function() {
313
+ listenForShortcuts(false);
314
+ },
315
+ onSourceOpen: function() {
316
+ sourceOpen = true;
317
+ },
318
+ onSourceClose: function() {
319
+ sourceOpen = false;
320
+ },
321
+ onDestroy: function() {
322
+ destroyed = true;
323
+ window.clearTimeout(timer);
324
+ timer = null;
325
+ stopObserving();
326
+ listenForShortcuts(false);
327
+ if (lastEditor === ge) {
328
+ lastEditor = null;
329
+ }
330
+ stack = [];
331
+ },
332
+ // Nothing of its own on the canvas
333
+ cleanMarkup: function() {
334
+ },
335
+ methods: {
336
+ undo: function() {
337
+ return go(-1, "api");
338
+ },
339
+ redo: function() {
340
+ return go(1, "api");
341
+ },
342
+ // A change waiting for its step counts: it is what an undo would undo
343
+ canUndo: function() {
344
+ return started && !destroyed && (index > 0 || pending());
345
+ },
346
+ // ... and it ends what could be redone
347
+ canRedo: function() {
348
+ return started && !destroyed && index < stack.length - 1 && !pending();
349
+ },
350
+ // What is there is the first step, with nothing before it or after it
351
+ clearHistory: function() {
352
+ if (!started || destroyed) {
353
+ return false;
354
+ }
355
+ window.clearTimeout(timer);
356
+ timer = null;
357
+ stack = [sourceOpen ? stack[index] : read()];
358
+ index = 0;
359
+ notify();
360
+ return true;
361
+ }
362
+ }
363
+ };
364
+ };
365
+ })();
@@ -0,0 +1 @@
1
+ (()=>{var m=window.GridEditor;if(!m||typeof m.get!="function")throw new Error("grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, its locales and grideditor.jquery.js");function j(t,r){return t?t.querySelector(r):null}function F(t,r){return t?Array.prototype.filter.call(t.children,function(i){return!r||i.matches(r)}):[]}function A(t,r){return!!t&&t.nodeType===1&&t.matches(r)}function I(t,r,i){return W(r).forEach(function(o){i===void 0?t.classList.toggle(o):t.classList.toggle(o,!!i)}),t}function C(t,r){return!!t&&t.nodeType===1&&t.classList.contains(r)}function W(t){return String(t||"").split(/\s+/).filter(Boolean)}Object.assign(m.locales.en,{"history.undo":"Undo","history.redo":"Redo"});var L=50,S=500,Z=".ge-tools-drawer, .ge-drop-marker",$=".ge-drag-chosen, .ge-drag-placeholder, .ge-drag-helper, .ge-resizing",g=[],x=null;function z(t){x=g.filter(function(r){return r.contains(t.target)})[0]||null}document.addEventListener("pointerdown",z,!0);document.addEventListener("focusin",z,!0);function J(t){var r=(t.key||"").toLowerCase();return/^[a-z]$/.test(r)||(r={KeyZ:"z",KeyY:"y"}[t.code]||""),r==="z"||r==="y"?r:null}function Q(){var t=document.activeElement;return!!t&&(A(t,"input, textarea, select")||t.isContentEditable)}function M(t){return F(t).filter(function(r){return!A(r,Z)})}m.features.history=function(t){var r=P(t.settings.history||{}),i=[],o=-1,s={canUndo:!1,canRedo:!1},a=null,h=null,b=!1,U=!1,c=!1,l=!1,f=!1;function P(e){var n={shortcuts:e.shortcuts===void 0?!0:e.shortcuts,buttons:e.buttons===void 0?!0:e.buttons,max:e.max===void 0?L:e.max,delay:e.delay===void 0?S:e.delay};return["shortcuts","buttons"].forEach(function(u){typeof n[u]!="boolean"&&(t.warn("history."+u+" is true or false: true is used"),n[u]=!0)}),(!Number.isInteger(n.max)||n.max<1)&&(t.warn("history.max must be a whole number of steps, 1 or more: "+L+" is used"),n.max=L),(!Number.isInteger(n.delay)||n.delay<0)&&(t.warn("history.delay must be a whole number of milliseconds, 0 or more: "+S+" is used"),n.delay=S),n}function w(){h.observe(t.canvas,{subtree:!0,childList:!0,attributes:!0,characterData:!0}),b=!0}function E(){h&&h.disconnect(),b=!1}function v(){var e=b;e&&E();var n=t.snapshotHtml();return e&&w(),n}function O(){return C(t.canvas,"ge-editing")}function k(){return C(t.canvas,"ge-dropping")||!!j(t.canvas,$)}function R(){window.clearTimeout(a),a=window.setTimeout(N,r.delay)}function B(){return a!==null&&v()!==i[o]}function N(){if(window.clearTimeout(a),a=null,!(!c||f||l)){if(k()){R();return}var e=v();e!==i[o]&&(i.splice(o+1),i.push(e),i.length>r.max+1&&i.shift(),o=i.length-1,y())}}function y(){var e={canUndo:o>0,canRedo:o<i.length-1},n=e.canUndo!==s.canUndo||e.canRedo!==s.canRedo;s=e,Y(),n&&t.emit("history-change",{canvas:t.canvas,canUndo:e.canUndo,canRedo:e.canRedo})}function Y(){t.toolbarItems("history").forEach(function(e){var n=e.getAttribute("data-ge-item")==="0"?!s.canUndo:!s.canRedo;I(e,"disabled",n),n?e.setAttribute("aria-disabled","true"):e.removeAttribute("aria-disabled")})}function D(e){if(!e||e===t.canvas||!t.canvas.contains(e))return null;for(var n=[],u=e;u!==t.canvas;u=u.parentElement)n.unshift(M(u.parentElement).indexOf(u));return n}function H(e){if(!e)return null;for(var n=t.canvas,u=0;u<e.length&&n;u++)n=M(n)[e[u]];return n||null}function d(e,n){if(!c||f||l||!O()||k())return!1;N();var u=o+e;if(u<0||u>=i.length)return!1;var G=D(t.getSelected()),_=D(t.getActiveTarget());E(),o=u,t.setHtml(i[o]),i[o]=t.snapshotHtml();var p=H(G);return p&&t.setSelected(p),p=H(_),p&&t.setActiveTarget(p),w(),y(),t.emit(e<0?"after-undo":"after-redo",{canvas:t.canvas,html:i[o],source:n}),!0}function K(e){if(!(x!==t||e.altKey||!(e.ctrlKey||e.metaKey))){var n=J(e);!n||n==="y"&&e.shiftKey||!O()||Q()||t.panelOpen()||d(n==="y"||e.shiftKey?1:-1,"shortcut")&&e.preventDefault()}}function T(e){var n=g.indexOf(t);e&&n===-1?(g.push(t),document.addEventListener("keydown",K)):!e&&n!==-1&&(g.splice(n,1),document.removeEventListener("keydown",K))}function q(){f||(i=[v()],o=0,h=new MutationObserver(R),w(),c=!0,y())}return{toolbar:function(){return r.buttons?[{labelKey:"history.undo",iconClass:"bi bi-arrow-counterclockwise",className:"ge-history-undo",align:"end",disabled:function(){return!s.canUndo},action:function(){d(-1,"tool")}},{labelKey:"history.redo",iconClass:"bi bi-arrow-clockwise",className:"ge-history-redo",align:"end",disabled:function(){return!s.canRedo},action:function(){d(1,"tool")}}]:[]},onInit:function(){r.shortcuts&&T(!0),!U&&(U=!0,Promise.resolve().then(q))},onDeinit:function(){T(!1)},onSourceOpen:function(){l=!0},onSourceClose:function(){l=!1},onDestroy:function(){f=!0,window.clearTimeout(a),a=null,E(),T(!1),x===t&&(x=null),i=[]},cleanMarkup:function(){},methods:{undo:function(){return d(-1,"api")},redo:function(){return d(1,"api")},canUndo:function(){return c&&!f&&(o>0||B())},canRedo:function(){return c&&!f&&o<i.length-1&&!B()},clearHistory:function(){return!c||f?!1:(window.clearTimeout(a),a=null,i=[l?i[o]:v()],o=0,y(),!0)}}}};})();
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/js/register.js", "../../src/js/dom.js", "../../src/js/plugins/grideditor.history.js"],
4
+ "sourcesContent": ["/**\n * What a plugin's or a locale's classic script imports in place of the\n * editor: the GridEditor the page already loaded.\n *\n * The build points `import { GridEditor } from '../grideditor.js'` here for\n * the classic scripts, so each registers on the editor the page has rather\n * than bringing its own. Loaded before the editor there is nothing to\n * register on, and saying so is better than a TypeError three calls later.\n */\nvar GridEditor = window.GridEditor;\n\nif (!GridEditor || typeof GridEditor.get !== 'function') {\n throw new Error('grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, ' +\n 'its locales and grideditor.jquery.js');\n}\n\nexport { GridEditor };\nexport default GridEditor;\n", "/**\n * The DOM, the way grid-editor uses it.\n *\n * Small helpers over the browser's own API, for the core and the shipped\n * plugins. Not a public API: a plugin of a host's own is free to write its\n * DOM code however it likes, and this file makes no promise to it.\n *\n * Stateless on purpose. The build puts a copy of this file in each plugin's\n * classic script, so anything kept here would be kept once per copy; a\n * module that needs to remember something about a node keeps its own\n * WeakMap.\n */\n\n/** Every element under `root` matching `selector`, as an array. */\nexport function all(root, selector) {\n return root ? Array.prototype.slice.call(root.querySelectorAll(selector)) : [];\n}\n\n/** The first element under `root` matching `selector`, or null. */\nexport function one(root, selector) {\n return root ? root.querySelector(selector) : null;\n}\n\n/** `root` itself when it matches, then every element under it that does. */\nexport function selfAndAll(root, selector) {\n if (!root) { return []; }\n\n var found = all(root, selector);\n if (root.nodeType === 1 && root.matches(selector)) { found.unshift(root); }\n return found;\n}\n\n/** The element children of `node`, those matching `selector` if one is given. */\nexport function children(node, selector) {\n if (!node) { return []; }\n\n return Array.prototype.filter.call(node.children, function(each) {\n return !selector || each.matches(selector);\n });\n}\n\n/** The first element child matching `selector`, or null. */\nexport function child(node, selector) {\n return children(node, selector)[0] || null;\n}\n\n/** The nearest ancestor-or-self matching `selector` below `stopAt`, or null. */\nexport function closest(node, selector, stopAt) {\n var found = node && node.nodeType === 1 ? node.closest(selector) : null;\n\n if (found && stopAt && found !== stopAt && !stopAt.contains(found)) { return null; }\n return found;\n}\n\n/** Whether `node` is an element matching `selector`. */\nexport function is(node, selector) {\n return !!node && node.nodeType === 1 && node.matches(selector);\n}\n\n/** Whether `node` is in the document. */\nexport function attached(node) {\n return !!node && document.documentElement.contains(node);\n}\n\n/** The element siblings after `node` that match `selector`. */\nexport function nextAll(node, selector) {\n var found = [];\n\n for (var next = node.nextElementSibling; next; next = next.nextElementSibling) {\n if (!selector || next.matches(selector)) { found.push(next); }\n }\n return found;\n}\n\n/** The ancestors of `node` up to, and not including, `stop`, nearest first. */\nexport function parentsUntil(node, stop) {\n var found = [];\n\n for (var parent = node.parentElement; parent && parent !== stop; parent = parent.parentElement) {\n found.push(parent);\n }\n return found;\n}\n\n/**\n * The nodes the html parses into, detached. Parsed the way innerHTML parses,\n * so a <script> in it never runs, here or once it is in the page.\n */\nexport function parse(html) {\n var template = document.createElement('template');\n template.innerHTML = html;\n return Array.prototype.slice.call(template.content.childNodes);\n}\n\n/** The one element the html is, detached. */\nexport function create(html) {\n var nodes = parse(html.trim());\n\n for (var i = 0; i < nodes.length; i++) {\n if (nodes[i].nodeType === 1) { return nodes[i]; }\n }\n return null;\n}\n\n/** An element of `tag`, with `attributes` and `text`. */\nexport function element(tag, attributes, text) {\n var node = document.createElement(tag);\n\n Object.keys(attributes || {}).forEach(function(name) {\n var value = attributes[name];\n if (value !== null && value !== undefined && value !== false) { node.setAttribute(name, value); }\n });\n if (text !== undefined && text !== null) { node.textContent = text; }\n\n return node;\n}\n\n/** Replace what `node` holds with `html`. A <script> in it does not run. */\nexport function setHtml(node, html) {\n node.innerHTML = html === undefined || html === null ? '' : String(html);\n return node;\n}\n\n/** Add one or several space separated classes. */\nexport function addClass(node, names) {\n split(names).forEach(function(name) { node.classList.add(name); });\n return node;\n}\n\nexport function removeClass(node, names) {\n split(names).forEach(function(name) { node.classList.remove(name); });\n return node;\n}\n\n/** Each class on, or off, or flipped when `on` is not given. */\nexport function toggleClass(node, names, state) {\n split(names).forEach(function(name) {\n if (state === undefined) {\n node.classList.toggle(name);\n } else {\n node.classList.toggle(name, !!state);\n }\n });\n return node;\n}\n\nexport function hasClass(node, name) {\n return !!node && node.nodeType === 1 && node.classList.contains(name);\n}\n\nfunction split(names) {\n return String(names || '').split(/\\s+/).filter(Boolean);\n}\n\n/** Take the class attribute off when there is nothing left in it. */\nexport function dropEmptyClass(node) {\n if (!node.getAttribute('class')) { node.removeAttribute('class'); }\n return node;\n}\n\n/** Take the style attribute off when there is nothing left in it. */\nexport function dropEmptyStyle(node) {\n if (!node.getAttribute('style')) { node.removeAttribute('style'); }\n return node;\n}\n\n/** Set attributes; a null or undefined value takes the attribute off. */\nexport function attr(node, attributes) {\n Object.keys(attributes).forEach(function(name) {\n var value = attributes[name];\n\n if (value === null || value === undefined) {\n node.removeAttribute(name);\n } else {\n node.setAttribute(name, value);\n }\n });\n return node;\n}\n\n/**\n * Inline styles, by css property name. A number is pixels for the properties\n * that take a length, as jQuery had it; an empty string takes one off.\n */\nexport function css(node, styles) {\n Object.keys(styles).forEach(function(property) {\n var value = styles[property];\n\n if (typeof value === 'number' && !UNITLESS[property]) { value = value + 'px'; }\n node.style.setProperty(property, value === null || value === undefined ? '' : String(value));\n });\n return node;\n}\n\nvar UNITLESS = { opacity: true, 'z-index': true, 'flex-grow': true, 'flex-shrink': true, order: true };\n\n/** What an element of this tag shows as when nothing hides it. */\nvar defaultDisplays = {};\n\nfunction defaultDisplay(node) {\n var tag = node.nodeName;\n\n if (!defaultDisplays[tag]) {\n var probe = document.body.appendChild(document.createElement(tag));\n defaultDisplays[tag] = getComputedStyle(probe).display;\n probe.remove();\n if (defaultDisplays[tag] === 'none') { defaultDisplays[tag] = 'block'; }\n }\n return defaultDisplays[tag];\n}\n\n/** Show a node: its inline display:none off, and a display of its own if a stylesheet hides it. */\nexport function show(node) {\n node.style.removeProperty('display');\n if (getComputedStyle(node).display === 'none') { node.style.display = defaultDisplay(node); }\n return node;\n}\n\nexport function hide(node) {\n node.style.display = 'none';\n return node;\n}\n\n/** Show or hide; flip when `on` is not given. */\nexport function toggle(node, state) {\n if (state === undefined) { state = !visible(node); }\n return state ? show(node) : hide(node);\n}\n\n/** Whether a node takes up room: what jQuery's :visible meant. */\nexport function visible(node) {\n return !!(node.offsetWidth || node.offsetHeight || node.getClientRects().length);\n}\n\n/** The border box's width, fractions included. */\nexport function outerWidth(node) {\n return node.getBoundingClientRect().width;\n}\n\nexport function outerHeight(node) {\n return node.getBoundingClientRect().height;\n}\n\n/** The content box's height, which is what jQuery's height() gave. */\nexport function contentHeight(node) {\n var style = getComputedStyle(node);\n return node.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);\n}\n\n/** Where a node is on the page, not in the window. */\nexport function offset(node) {\n var box = node.getBoundingClientRect();\n return { top: box.top + window.pageYOffset, left: box.left + window.pageXOffset };\n}\n\n/** Its index among its parent's element children, those matching `selector` if given. */\nexport function indexIn(node, selector) {\n return children(node.parentElement, selector).indexOf(node);\n}\n\n/** Put `node` right after `reference`. */\nexport function insertAfter(node, reference) {\n reference.parentNode.insertBefore(node, reference.nextSibling);\n return node;\n}\n\n/** Replace `node` with what it holds. */\nexport function unwrap(node) {\n var parent = node.parentNode;\n\n while (node.firstChild) { parent.insertBefore(node.firstChild, node); }\n node.remove();\n}\n\n/** Put `node` inside `wrapper`, where `node` was. */\nexport function wrap(node, wrapper) {\n node.parentNode.insertBefore(wrapper, node);\n wrapper.appendChild(node);\n return wrapper;\n}\n\n/**\n * Listen on `root` for `types` (space separated) on descendants that match\n * `selector`, with `this` the matched element, as jQuery's delegated\n * handlers had it. Hands back the listener, for removeEventListener; a\n * signal in `options` removes it too.\n */\nexport function delegate(root, types, selector, handler, options) {\n var listener = function(event) {\n var match = closest(event.target, selector, root);\n if (match && root.contains(match)) { return handler.call(match, event); }\n return undefined;\n };\n\n split(types).forEach(function(type) { root.addEventListener(type, listener, options); });\n return listener;\n}\n\n/** Listen for several space separated event types with one handler. */\nexport function on(target, types, handler, options) {\n split(types).forEach(function(type) { target.addEventListener(type, handler, options); });\n return handler;\n}\n\nexport function off(target, types, handler, options) {\n split(types).forEach(function(type) { target.removeEventListener(type, handler, options); });\n}\n\n/** A mouse event of `type` sent to `node`, the way a pointer would. */\nexport function fire(node, type) {\n node.dispatchEvent(new MouseEvent(type, { bubbles: type !== 'mouseenter' && type !== 'mouseleave' }));\n}\n\n/* ------------------------------------------------------------------\n * Taking a node away, visibly.\n *\n * The durations are jQuery's: 400 ms unless the caller says otherwise. `done` runs once the node is out of sight, which is\n * when the caller removes it; a node that is already out of sight is done at\n * once, on the next task, as jQuery had it.\n * ------------------------------------------------------------------ */\n\nvar DEFAULT_DURATION = 400;\n\nfunction animateAway(node, frames, duration, done) {\n duration = duration === undefined ? DEFAULT_DURATION : duration;\n\n var finish = function() {\n hide(node);\n if (done) { done(); }\n };\n\n if (!visible(node) || !node.animate || duration <= 0) {\n window.setTimeout(finish, 0);\n return;\n }\n\n var animation = node.animate(frames(getComputedStyle(node)), {\n duration: duration,\n easing: 'ease-in-out',\n });\n var called = false;\n var once = function() {\n if (called) { return; }\n called = true;\n finish();\n };\n\n animation.onfinish = once;\n animation.oncancel = once;\n}\n\n/** Fold a node up to nothing, then hide it. */\nexport function slideUp(node, duration, done) {\n if (typeof duration === 'function') {\n done = duration;\n duration = undefined;\n }\n\n node.style.overflow = 'hidden';\n animateAway(node, function(style) {\n return [\n {\n height: style.height,\n paddingTop: style.paddingTop,\n paddingBottom: style.paddingBottom,\n marginTop: style.marginTop,\n marginBottom: style.marginBottom,\n },\n { height: '0px', paddingTop: '0px', paddingBottom: '0px', marginTop: '0px', marginBottom: '0px' },\n ];\n }, duration, function() {\n node.style.removeProperty('overflow');\n if (done) { done(); }\n });\n}\n\n/** Fade a node out, then hide it. */\nexport function fadeOut(node, duration, done) {\n animateAway(node, function(style) {\n return [{ opacity: style.opacity }, { opacity: 0 }];\n }, duration, done);\n}\n\n/** Fade and fold a node, in height and, with `width`, sideways too, then hide it. */\nexport function shrinkAway(node, duration, done, width) {\n node.style.overflow = 'hidden';\n animateAway(node, function(style) {\n var from = { opacity: style.opacity, height: style.height };\n var to = { opacity: 0, height: '0px' };\n\n if (width) {\n from.width = style.width;\n to.width = '0px';\n }\n return [from, to];\n }, duration, function() {\n node.style.removeProperty('overflow');\n if (done) { done(); }\n });\n}\n", "/**\n * Undo and redo for grid-editor.\n *\n * A feature plugin: load this file after the editor and what is edited is\n * kept, step by step, in memory, so Ctrl+Z goes back to how it was and\n * Ctrl+Y (or Ctrl+Shift+Z) comes forward again. Two buttons on the right of\n * the toolbar do the same. What it can ask the editor for is the handle its\n * factory is called with, described in docs/plugins.md.\n *\n * <script src=\"dist/grideditor.min.js\"></script>\n * <script src=\"dist/plugins/grideditor.history.min.js\"></script>\n *\n * A step is the canvas's markup as ge.snapshotHtml() gives it, kept `delay`\n * ms after the last change: a drag or a burst of typing is one step. Going\n * to one puts its markup back with ge.setHtml(), and the node that was\n * selected, and the target, are chosen again by where they were.\n *\n * Typing in a text is the text editor's own to undo while it has the focus:\n * the keys are left alone then, and what was typed becomes a step when it\n * is done.\n *\n * new GridEditor('#myGrid', { history: { max: 100, buttons: false } });\n */\nimport { GridEditor } from '../grideditor.js';\nimport * as dom from '../dom.js';\n\nObject.assign(GridEditor.locales.en, {\n 'history.undo': 'Undo',\n 'history.redo': 'Redo',\n});\n\nvar DEFAULT_MAX = 50;\nvar DEFAULT_DELAY = 500;\n\n/** What the editor puts in a node that is not the node's own: a position counts without it. */\nvar FURNITURE = '.ge-tools-drawer, .ge-drop-marker';\n\n/** What a drag or a resize leaves on the canvas while it lasts. */\nvar BUSY = '.ge-drag-chosen, .ge-drag-placeholder, .ge-drag-helper, .ge-resizing';\n\n/** The editors listening for the keys, and the one used last, which a key press goes to. */\nvar listeningEditors = [];\nvar lastEditor = null;\n\nfunction track(e) {\n lastEditor = listeningEditors.filter(function(ge) { return ge.contains(e.target); })[0] || null;\n}\n\n// Captured: a control that stops the event still counts as using its editor\ndocument.addEventListener('pointerdown', track, true);\ndocument.addEventListener('focusin', track, true);\n\n/** 'z', 'y' or null, from the letter typed or, on a layout without Latin letters, the key's place. */\nfunction shortcutKey(e) {\n var key = (e.key || '').toLowerCase();\n if (!/^[a-z]$/.test(key)) { key = { KeyZ: 'z', KeyY: 'y' }[e.code] || ''; }\n\n return key === 'z' || key === 'y' ? key : null;\n}\n\nfunction typing() {\n var focus = document.activeElement;\n return !!focus && (dom.is(focus, 'input, textarea, select') || focus.isContentEditable);\n}\n\n/** The children of `parent` that are its own content, without the editor's furniture. */\nfunction contentOf(parent) {\n return dom.children(parent).filter(function(child) { return !dom.is(child, FURNITURE); });\n}\n\nGridEditor.features.history = function(ge) {\n\n var options = optionsFrom(ge.settings.history || {});\n\n var stack = []; // The markup of each step, oldest first\n var index = -1; // The step the canvas is at\n var shown = { canUndo: false, canRedo: false }; // What the last history-change said\n var timer = null;\n var observer = null;\n var observing = false;\n var initiated = false; // Whether the first init has been looked at\n var started = false; // Whether there is a first step to go back to\n var sourceOpen = false;\n var destroyed = false;\n\n function optionsFrom(given) {\n var result = {\n shortcuts: given.shortcuts === undefined ? true : given.shortcuts,\n buttons: given.buttons === undefined ? true : given.buttons,\n max: given.max === undefined ? DEFAULT_MAX : given.max,\n delay: given.delay === undefined ? DEFAULT_DELAY : given.delay,\n };\n\n ['shortcuts', 'buttons'].forEach(function(name) {\n if (typeof result[name] !== 'boolean') {\n ge.warn('history.' + name + ' is true or false: true is used');\n result[name] = true;\n }\n });\n if (!Number.isInteger(result.max) || result.max < 1) {\n ge.warn('history.max must be a whole number of steps, 1 or more: ' + DEFAULT_MAX + ' is used');\n result.max = DEFAULT_MAX;\n }\n if (!Number.isInteger(result.delay) || result.delay < 0) {\n ge.warn('history.delay must be a whole number of milliseconds, 0 or more: ' + DEFAULT_DELAY + ' is used');\n result.delay = DEFAULT_DELAY;\n }\n\n return result;\n }\n\n function observe() {\n observer.observe(ge.canvas, { subtree: true, childList: true, attributes: true, characterData: true });\n observing = true;\n }\n\n function stopObserving() {\n if (observer) { observer.disconnect(); }\n observing = false;\n }\n\n /** The canvas's markup now. Reading may change the canvas, when a plugin cannot be read on a copy, and that is no change of the user's. */\n function read() {\n var watching = observing;\n if (watching) { stopObserving(); }\n var html = ge.snapshotHtml();\n if (watching) { observe(); }\n\n return html;\n }\n\n function editing() {\n return dom.hasClass(ge.canvas, 'ge-editing');\n }\n\n /** A drag or a resize is going on: the canvas is not at a step but between two. */\n function busy() {\n return dom.hasClass(ge.canvas, 'ge-dropping') || !!dom.one(ge.canvas, BUSY);\n }\n\n function schedule() {\n window.clearTimeout(timer);\n timer = window.setTimeout(commit, options.delay);\n }\n\n /** Whether something changed since the last step that is not a step yet. */\n function pending() {\n return timer !== null && read() !== stack[index];\n }\n\n /** What is on the canvas, if it is not the step it is at, becomes the next one. */\n function commit() {\n window.clearTimeout(timer);\n timer = null;\n\n if (!started || destroyed || sourceOpen) { return; }\n if (busy()) { schedule(); return; }\n\n var html = read();\n if (html === stack[index]) { return; }\n\n stack.splice(index + 1);\n stack.push(html);\n if (stack.length > options.max + 1) { stack.shift(); }\n index = stack.length - 1;\n\n notify();\n }\n\n /** The buttons, and a history-change when what can be done is not what was last said. */\n function notify() {\n var now = { canUndo: index > 0, canRedo: index < stack.length - 1 };\n var changed = now.canUndo !== shown.canUndo || now.canRedo !== shown.canRedo;\n\n shown = now;\n paint();\n\n if (changed) {\n ge.emit('history-change', { canvas: ge.canvas, canUndo: now.canUndo, canRedo: now.canRedo });\n }\n }\n\n function paint() {\n ge.toolbarItems('history').forEach(function(button) {\n var off = button.getAttribute('data-ge-item') === '0' ? !shown.canUndo : !shown.canRedo;\n\n dom.toggleClass(button, 'disabled', off);\n if (off) {\n button.setAttribute('aria-disabled', 'true');\n } else {\n button.removeAttribute('aria-disabled');\n }\n });\n }\n\n /**\n * Where a node is: its index among its parent's content, all the way up\n * from the canvas. The canvas is made again from markup, so a node is\n * not the same one afterwards, but the place it was in is.\n */\n function pathOf(node) {\n if (!node || node === ge.canvas || !ge.canvas.contains(node)) { return null; }\n\n var path = [];\n for (var at = node; at !== ge.canvas; at = at.parentElement) {\n path.unshift(contentOf(at.parentElement).indexOf(at));\n }\n\n return path;\n }\n\n function resolve(path) {\n if (!path) { return null; }\n\n var node = ge.canvas;\n for (var i = 0; i < path.length && node; i++) { node = contentOf(node)[path[i]]; }\n\n return node || null;\n }\n\n function go(step, source) {\n if (!started || destroyed || sourceOpen || !editing() || busy()) { return false; }\n\n // A change not yet a step is a step of its own, and the one undone\n commit();\n\n var to = index + step;\n if (to < 0 || to >= stack.length) { return false; }\n\n var selected = pathOf(ge.getSelected());\n var target = pathOf(ge.getActiveTarget());\n\n stopObserving();\n index = to;\n ge.setHtml(stack[index]);\n // The step as the canvas reads it back, so that is what is compared with\n stack[index] = ge.snapshotHtml();\n\n var node = resolve(selected);\n if (node) { ge.setSelected(node); }\n node = resolve(target);\n if (node) { ge.setActiveTarget(node); }\n\n observe();\n notify();\n ge.emit(step < 0 ? 'after-undo' : 'after-redo', { canvas: ge.canvas, html: stack[index], source: source });\n\n return true;\n }\n\n function onKeydown(e) {\n if (lastEditor !== ge || e.altKey || !(e.ctrlKey || e.metaKey)) { return; }\n\n var key = shortcutKey(e);\n if (!key || (key === 'y' && e.shiftKey)) { return; }\n if (!editing() || typing() || ge.panelOpen()) { return; }\n\n // Left alone, it is the browser's\n if (go(key === 'y' || e.shiftKey ? 1 : -1, 'shortcut')) { e.preventDefault(); }\n }\n\n function listenForShortcuts(on) {\n var at = listeningEditors.indexOf(ge);\n\n if (on && at === -1) {\n listeningEditors.push(ge);\n document.addEventListener('keydown', onKeydown);\n } else if (!on && at !== -1) {\n listeningEditors.splice(at, 1);\n document.removeEventListener('keydown', onKeydown);\n }\n }\n\n /** After the first init has finished: the canvas is ready to be read. */\n function start() {\n if (destroyed) { return; }\n\n stack = [read()];\n index = 0;\n observer = new MutationObserver(schedule);\n observe();\n started = true;\n notify();\n }\n\n return {\n toolbar: function() {\n if (!options.buttons) { return []; }\n\n return [\n {\n labelKey: 'history.undo',\n iconClass: 'bi bi-arrow-counterclockwise',\n className: 'ge-history-undo',\n align: 'end',\n disabled: function() { return !shown.canUndo; },\n action: function() { go(-1, 'tool'); },\n },\n {\n labelKey: 'history.redo',\n iconClass: 'bi bi-arrow-clockwise',\n className: 'ge-history-redo',\n align: 'end',\n disabled: function() { return !shown.canRedo; },\n action: function() { go(1, 'tool'); },\n },\n ];\n },\n\n onInit: function() {\n if (options.shortcuts) { listenForShortcuts(true); }\n\n if (initiated) { return; }\n initiated = true;\n Promise.resolve().then(start);\n },\n\n // A canvas out of editing leaves the keys to the page: it is read again when it is back\n onDeinit: function() { listenForShortcuts(false); },\n\n onSourceOpen: function() { sourceOpen = true; },\n onSourceClose: function() { sourceOpen = false; },\n\n onDestroy: function() {\n destroyed = true;\n window.clearTimeout(timer);\n timer = null;\n stopObserving();\n listenForShortcuts(false);\n if (lastEditor === ge) { lastEditor = null; }\n stack = [];\n },\n\n // Nothing of its own on the canvas\n cleanMarkup: function() {},\n\n methods: {\n undo: function() { return go(-1, 'api'); },\n\n redo: function() { return go(1, 'api'); },\n\n // A change waiting for its step counts: it is what an undo would undo\n canUndo: function() {\n return started && !destroyed && (index > 0 || pending());\n },\n\n // ... and it ends what could be redone\n canRedo: function() {\n return started && !destroyed && index < stack.length - 1 && !pending();\n },\n\n // What is there is the first step, with nothing before it or after it\n clearHistory: function() {\n if (!started || destroyed) { return false; }\n\n window.clearTimeout(timer);\n timer = null;\n // The textarea's markup is not the canvas's while the source is open\n stack = [sourceOpen ? stack[index] : read()];\n index = 0;\n notify();\n\n return true;\n },\n },\n };\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["GridEditor", "one", "root", "selector", "children", "node", "selector", "each", "is", "node", "selector", "toggleClass", "node", "names", "state", "split", "name", "hasClass", "GridEditor", "DEFAULT_MAX", "DEFAULT_DELAY", "FURNITURE", "BUSY", "listeningEditors", "lastEditor", "track", "e", "ge", "shortcutKey", "key", "typing", "focus", "is", "contentOf", "parent", "children", "child", "options", "optionsFrom", "stack", "index", "shown", "timer", "observer", "observing", "initiated", "started", "sourceOpen", "destroyed", "given", "result", "name", "observe", "stopObserving", "read", "watching", "html", "editing", "hasClass", "busy", "one", "schedule", "commit", "pending", "notify", "now", "changed", "paint", "button", "off", "toggleClass", "pathOf", "node", "path", "at", "resolve", "i", "go", "step", "source", "to", "selected", "target", "onKeydown", "listenForShortcuts", "on", "start"]
7
+ }
package/docs/events.md CHANGED
@@ -97,6 +97,9 @@ about every insertion can bind `grideditor:before-add` and switch on
97
97
  | `grideditor:after-autosave` | — | no | with the autosave plugin, after a draft is written |
98
98
  | `grideditor:after-restore-draft` | — | no | with the autosave plugin, after the user chose to restore a draft and the canvas is it |
99
99
  | `grideditor:autosave-error` | — | no | with the autosave plugin, when the storage refused a draft |
100
+ | `grideditor:after-undo` | — | no | with the history plugin, after the canvas went back a step: `source` is `'shortcut'`, `'tool'` or `'api'`, and the payload has `canvas` and `html`, the markup it went to |
101
+ | `grideditor:after-redo` | — | no | with the history plugin, after the canvas came forward a step; the same payload |
102
+ | `grideditor:history-change` | — | no | with the history plugin, after what can be done changes: `canvas`, `canUndo`, `canRedo`. Not for a step that leaves both as they were |
100
103
 
101
104
  Two things here are not in the 3.0 specification's catalogue. The indent pair,
102
105
  because the indent tools are an operation like any other and announce
@@ -229,6 +229,17 @@ the same html saved.
229
229
  | `autosave.discard` | Discard | The modal's cancel button: the draft is taken away |
230
230
 
231
231
 
232
+ history.*
233
+ ---------
234
+
235
+ The history plugin: undo and redo.
236
+
237
+ | Key | English | Where |
238
+ | --- | --- | --- |
239
+ | `history.undo` | Undo | Title of the toolbar's undo button |
240
+ | `history.redo` | Redo | Title of the toolbar's redo button |
241
+
242
+
232
243
  codemirror.*
233
244
  ------------
234
245
 
package/docs/plugins.md CHANGED
@@ -173,6 +173,7 @@ element, and where there may be several, an array.
173
173
  | `ge.refreshToolbar()` | The toolbar built again, from what the feature plugins' `toolbar`s hold now: for a plugin whose buttons come and go. The category shown with `toolbar_groups`, the preview and the source view stay as they are. Does nothing before the toolbar is first built, and after `destroy` |
174
174
  | `ge.getSelected()` | The node the sidebar shows, as the public method gives it: `null` without the `'sidebar'` panel |
175
175
  | `ge.getActiveTarget()` | The column or section the toolbar adds to, as the public method gives it: `null` without `active_target` |
176
+ | `ge.setSelected(node)` / `ge.setActiveTarget(node)` | Choose the node the sidebar shows, or the column or section the toolbar adds to: the public methods without their warnings. `true` when it took, `false` for a node that is not one, or without the `'sidebar'` panel or `active_target`. A plugin that puts the choice back after it remade the canvas does not know the node is still one |
176
177
  | `ge.panelOpen()` | Whether a settings panel is open in a popover, an offcanvas or a modal, or one is unfolded inline in a drawer, or the editor's dialog or delete confirmation is up. The sidebar does not count: it is always there |
177
178
  | `ge.contains(node)` | Whether a node is this editor's: in its canvas, its toolbar, its settings panels (the sidebar too, when it has it), its dialog or its confirmation |
178
179
  | `ge.toolbarItems(name)` | An array of the toolbar buttons the feature plugin `name` declared, for showing and hiding them |
@@ -384,7 +385,13 @@ rather than remembering anything about it.
384
385
  button. `hidden`, a boolean or a function called whenever the toolbar is
385
386
  built, builds the button hidden: a plugin that shows and hides its buttons
386
387
  gives it, so a toolbar built again - `ge.refreshToolbar()`, `setLocale` -
387
- shows them as they are. With `inColumn: true` what it makes belongs in a column, as a
388
+ shows them as they are. With **`action(button)`** in place of `create` the
389
+ button is not a block to add: a click calls `action`, which makes nothing and
390
+ asks nothing of the active target, and the button is not carried onto the
391
+ canvas. **`disabled`**, a boolean or a function, builds it disabled, as
392
+ `hidden` does; afterwards the plugin gives its buttons - `ge.toolbarItems(name)` -
393
+ the `disabled` class and `aria-disabled="true"`, and a click on one with the
394
+ class does nothing. With `inColumn: true` what it makes belongs in a column, as a
388
395
  container or a text does: a click puts it in a row and a column of its own
389
396
  at the end of the canvas, and a drop anywhere but in a column does the
390
397
  same where it was dropped.
@@ -34,6 +34,10 @@
34
34
  // tinyMCE 7 is GPL-2.0-or-later: 'gpl' accepts that licence.
35
35
  // A commercial licence passes its own key here instead.
36
36
  tinymce: { config: { license_key: 'gpl' } },
37
+ // A click in a column or a section makes it the target, which
38
+ // turns on Ctrl+C and Ctrl+V. clipboard: { shortcuts: false }
39
+ // would leave the keys to the browser
40
+ active_target: true,
37
41
  };
38
42
 
39
43
  ['#myGrid', '#otherGrid'].forEach(function(selector) {
@@ -46,6 +50,12 @@
46
50
  document.getElementById('log').textContent = 'Pasted a ' + e.detail.kind + ' into ' + e.currentTarget.id;
47
51
  }
48
52
  });
53
+
54
+ // A copy says where it came from: 'tool' or 'shortcut'
55
+ grid.addEventListener('grideditor:after-copy', function(e) {
56
+ document.getElementById('log').textContent = 'Copied a ' + e.detail.kind + ' from ' + e.currentTarget.id +
57
+ (e.detail.source === 'shortcut' ? ' with Ctrl+C' : ' with its tool');
58
+ });
49
59
  });
50
60
  });
51
61
  </script>
@@ -70,6 +80,18 @@
70
80
  <i class="bi bi-clipboard-plus"></i> on the right: click it, or
71
81
  drag it to where the copy should land.
72
82
  </p>
83
+ <p class="text-body-secondary">
84
+ With the keyboard: click a column or a section - its background or
85
+ its drawer, not a text, which opens the text for writing - and it
86
+ becomes the target, with a dashed outline. <kbd>Ctrl</kbd>+<kbd>C</kbd>
87
+ (<kbd>⌘</kbd>+<kbd>C</kbd> on a Mac) copies it, and
88
+ <kbd>Ctrl</kbd>+<kbd>V</kbd> pastes what is copied into it, where
89
+ it fits: a row, a text, a container or an element at the end of a
90
+ column, a row into a section, a section after the block the target
91
+ is in. While you are writing in a text, or have text selected, the
92
+ keys copy and paste the text as usual. Escape, or a click on the
93
+ canvas outside any column, clears the target.
94
+ </p>
73
95
  <p class="text-body-secondary">
74
96
  What is copied is kept in the browser, so it can be pasted in the
75
97
  other editor, after a reload, or in this page open in another tab.
@@ -0,0 +1,94 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+
6
+ <!-- CSS dependencies -->
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css">
9
+
10
+ <!-- Javascript dependencies -->
11
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
12
+
13
+ <!-- Grid editor CSS -->
14
+ <link rel="stylesheet" type="text/css" href="../dist/grideditor.css" />
15
+ <!-- Grid editor javascript -->
16
+ <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
17
+ <script src="../dist/grideditor.min.js"></script>
18
+ <!-- Undo and redo are a plugin of its own, loaded after the editor. The
19
+ others are here so there is a bit of everything to undo. -->
20
+ <script src="../dist/plugins/grideditor.history.min.js"></script>
21
+ <script src="../dist/plugins/grideditor.clipboard.min.js"></script>
22
+ <script src="../dist/plugins/grideditor.tabs.min.js"></script>
23
+ <script src="../dist/plugins/grideditor.elements.min.js"></script>
24
+
25
+ <script>
26
+ document.addEventListener('DOMContentLoaded', function() {
27
+ var grid = document.getElementById('myGrid');
28
+ var log = document.getElementById('log');
29
+
30
+ new GridEditor(grid, {
31
+ new_row_layouts: [[12], [6, 6], [4, 4, 4]],
32
+ content_types: [],
33
+ // What was selected is selected again after an undo
34
+ settings_panel: 'sidebar',
35
+ history: {
36
+ max: 50, // the steps that can be undone
37
+ delay: 500, // a drag or a burst of changes is one step
38
+ },
39
+ });
40
+
41
+ // A step done says where it came from: 'shortcut', 'tool' or 'api'
42
+ ['undo', 'redo'].forEach(function(name) {
43
+ grid.addEventListener('grideditor:after-' + name, function(e) {
44
+ log.textContent = name + ' (' + e.detail.source + ')';
45
+ });
46
+ });
47
+ });
48
+ </script>
49
+
50
+ <title>Grid Editor example - undo and redo</title>
51
+ </head>
52
+ <body>
53
+ <!-- Back to the demo index -->
54
+ <div class="container pt-3">
55
+ <a class="link-secondary" href="index.html"><i class="bi bi-arrow-left"></i> All demos</a>
56
+ </div>
57
+
58
+ <div class="container py-3">
59
+ <h2>Undo and redo</h2>
60
+ <p class="text-body-secondary">
61
+ Delete a row, drag a column to another size, paste something, change
62
+ an id in the panel: each is a step. <kbd>Ctrl</kbd>+<kbd>Z</kbd>
63
+ (<kbd>⌘</kbd>+<kbd>Z</kbd> on a Mac) goes back one, and
64
+ <kbd>Ctrl</kbd>+<kbd>Y</kbd> or <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd>
65
+ comes forward again, as do the <i class="bi bi-arrow-counterclockwise"></i>
66
+ and <i class="bi bi-arrow-clockwise"></i> buttons on the right of
67
+ the toolbar. A change made after going back ends what could be redone.
68
+ </p>
69
+ <p class="text-body-secondary">
70
+ While you are writing in a text or a field, the keys undo what you
71
+ typed there, as usual; it becomes a step when you are done. The
72
+ column or row selected in the sidebar is selected again after a step.
73
+ <span id="log" class="fw-semibold"></span>
74
+ </p>
75
+
76
+ <div id="myGrid">
77
+ <div class="row">
78
+ <div class="col-lg-6">
79
+ <h3>Change this page</h3>
80
+ <p>Then undo it.</p>
81
+ </div>
82
+ <div class="col-lg-6">
83
+ <p>Delete this column, or the whole row.</p>
84
+ </div>
85
+ </div>
86
+ <div class="row">
87
+ <div class="col-lg-4"><p>One.</p></div>
88
+ <div class="col-lg-4"><p>Two.</p></div>
89
+ <div class="col-lg-4"><p>Three.</p></div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </body>
94
+ </html>