@themarioga/grid-editor 8.2.0 → 8.4.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 +31 -0
- package/README.md +25 -1
- package/dist/grideditor.bundle.min.js +3 -3
- package/dist/grideditor.css +116 -0
- package/dist/grideditor.d.ts +6 -0
- package/dist/grideditor.esm.js +301 -5
- package/dist/grideditor.js +301 -5
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/grideditor.min.js +2 -2
- package/dist/grideditor.min.js.map +3 -3
- package/dist/locales/grideditor.es.esm.js +4 -0
- package/dist/locales/grideditor.es.js +4 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +2 -2
- package/dist/plugins/grideditor.ckeditor.esm.js +1 -0
- package/dist/plugins/grideditor.ckeditor.js +1 -0
- package/dist/plugins/grideditor.ckeditor.min.js +1 -1
- package/dist/plugins/grideditor.ckeditor.min.js.map +2 -2
- package/dist/plugins/grideditor.sections.esm.js +1 -0
- package/dist/plugins/grideditor.sections.js +1 -0
- package/dist/plugins/grideditor.sections.min.js +1 -1
- package/dist/plugins/grideditor.sections.min.js.map +2 -2
- package/dist/plugins/grideditor.summernote.esm.js +1 -0
- package/dist/plugins/grideditor.summernote.js +1 -0
- package/dist/plugins/grideditor.summernote.min.js +1 -1
- package/dist/plugins/grideditor.summernote.min.js.map +2 -2
- package/dist/plugins/grideditor.tinymce.esm.js +1 -0
- package/dist/plugins/grideditor.tinymce.js +1 -0
- package/dist/plugins/grideditor.tinymce.min.js +1 -1
- package/dist/plugins/grideditor.tinymce.min.js.map +2 -2
- package/docs/locale-keys.md +4 -0
- package/docs/plugins.md +22 -0
- package/package.json +1 -1
- package/src/js/grideditor.js +359 -4
- package/src/js/locales/grideditor.es.js +4 -0
- package/src/js/plugins/grideditor.sections.js +1 -0
- package/src/js/text/grideditor.text.js +1 -0
- package/src/less/grideditor.less +134 -0
- package/types/grideditor.d.ts +6 -0
package/src/js/grideditor.js
CHANGED
|
@@ -198,6 +198,15 @@ function translate(settings, key, params) {
|
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/** Whether some locale has a string for the key, asked without the warning a missing one gets. */
|
|
202
|
+
function hasString(settings, key) {
|
|
203
|
+
var locales = GridEditor.locales;
|
|
204
|
+
|
|
205
|
+
return (settings.locale_strings || {})[key] !== undefined ||
|
|
206
|
+
(locales[settings.locale] || {})[key] !== undefined ||
|
|
207
|
+
locales.en[key] !== undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
201
210
|
function warn(message) {
|
|
202
211
|
if (window.console && window.console.warn) {
|
|
203
212
|
window.console.warn('grid-editor: ' + message);
|
|
@@ -366,6 +375,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
366
375
|
'row_tools' : [],
|
|
367
376
|
'drag_handle' : 'tool', // 'tool' for the move tool, 'drawer' for the whole drawer
|
|
368
377
|
'toolbar_drag' : 'auto', // Drag the toolbar's buttons onto the canvas. 'auto' follows drag_handle
|
|
378
|
+
'toolbar_overflow' : 'menu', // What doesn't fit on one line: 'menu' behind a button, 'wrap' onto another line
|
|
379
|
+
'drawer_overflow' : 'menu', // The same for the drawers' tools: 'menu' behind a button, 'wrap' onto another line
|
|
380
|
+
'toolbar_groups' : false, // The add buttons in categories, one shown at a time, picked with tabs
|
|
369
381
|
'active_target' : false, // A click in a column makes it where the toolbar's buttons add
|
|
370
382
|
'element_tools' : [], // Host tools on element drawers, same shape as row_tools
|
|
371
383
|
'element_classes' : [], // Preset class toggles on an element's settings panel
|
|
@@ -420,6 +432,11 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
420
432
|
var sourceOpen = false; // Whether the canvas is being edited as html
|
|
421
433
|
var dropMarker = null; // The line showing where a dragged toolbar button would land
|
|
422
434
|
var activeTarget = null; // The column or region the toolbar adds to, with active_target
|
|
435
|
+
var toolbarLifetime = null; // Aborted when the toolbar is rebuilt or taken away
|
|
436
|
+
var closeOverflowMenu = function() {}; // Folds the toolbar's overflow menu away, when it has one
|
|
437
|
+
var refitToolbar = function() {}; // Works out again what the toolbar's overflow menu holds, when it has one
|
|
438
|
+
var addButtons = []; // The toolbar's add buttons, in toolbar order: { button, home, group }
|
|
439
|
+
var toolbarGroup = null; // The category shown, with toolbar_groups. Kept when the toolbar is rebuilt
|
|
423
440
|
var warnedHere = {}; // Some warnings are worth saying once per instance, not once per call
|
|
424
441
|
var sortables = []; // Every list made sortable, so deinit destroys exactly those
|
|
425
442
|
var instanceId = ++editorCounter; // Scopes the sortable groups to this editor
|
|
@@ -464,7 +481,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
464
481
|
removeConfirmModal();
|
|
465
482
|
removeSettingsPanels();
|
|
466
483
|
removeDialog();
|
|
467
|
-
|
|
484
|
+
removeMainControls();
|
|
468
485
|
createMainControls();
|
|
469
486
|
reset();
|
|
470
487
|
}
|
|
@@ -969,6 +986,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
969
986
|
canvas.parentNode.insertBefore(htmlTextArea, canvas);
|
|
970
987
|
|
|
971
988
|
createMainControls();
|
|
989
|
+
watchDrawers();
|
|
972
990
|
|
|
973
991
|
var signal = { signal: lifetime.signal };
|
|
974
992
|
|
|
@@ -1000,6 +1018,16 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1000
1018
|
if (!dom.hasClass(canvas, 'ge-editing')) { return; }
|
|
1001
1019
|
if (from && Math.abs(e.clientX - from.x) + Math.abs(e.clientY - from.y) > settings.drag.threshold) { return; }
|
|
1002
1020
|
var region = dom.closest(e.target, targetSelector());
|
|
1021
|
+
|
|
1022
|
+
// The target's own background, or its drawer's, clicked
|
|
1023
|
+
// again takes it back. What is in it - a text, a tool, a
|
|
1024
|
+
// nested column - keeps it, or makes that the target
|
|
1025
|
+
if (region && region === activeTarget &&
|
|
1026
|
+
(e.target === region || e.target === dom.child(region, '.ge-tools-drawer'))) {
|
|
1027
|
+
changeTarget(null);
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1003
1031
|
changeTarget(region && region !== canvas && canvas.contains(region) ? region : null);
|
|
1004
1032
|
}, signal);
|
|
1005
1033
|
|
|
@@ -1035,8 +1063,19 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1035
1063
|
htmlTextArea.parentNode.insertBefore(mainControls, htmlTextArea);
|
|
1036
1064
|
wrapper = mainControls.appendChild(dom.element('div', { 'class': 'ge-wrapper ge-top' }));
|
|
1037
1065
|
|
|
1066
|
+
// With the overflow menu, the add buttons stand in a line of
|
|
1067
|
+
// their own that keeps to one line, beside the menu's button
|
|
1068
|
+
var overflowMenu = settings.toolbar_overflow !== 'wrap';
|
|
1069
|
+
var start = wrapper;
|
|
1070
|
+
if (overflowMenu) {
|
|
1071
|
+
dom.addClass(wrapper, 'ge-toolbar-menu');
|
|
1072
|
+
start = wrapper.appendChild(dom.element('div', { 'class': 'ge-toolbar-start' }));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
addButtons = [];
|
|
1076
|
+
|
|
1038
1077
|
// Add row
|
|
1039
|
-
addRowGroup =
|
|
1078
|
+
addRowGroup = start.appendChild(dom.element('div', { 'class': 'ge-addRowGroup btn-group' }));
|
|
1040
1079
|
addContainerGroup = dom.element('div', { 'class': 'ge-addContainerGroup btn-group ms-1' });
|
|
1041
1080
|
settings.new_row_layouts.forEach(function(layout) {
|
|
1042
1081
|
var grouped = !Array.isArray(layout);
|
|
@@ -1065,6 +1104,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1065
1104
|
}
|
|
1066
1105
|
});
|
|
1067
1106
|
addRowGroup.appendChild(btn);
|
|
1107
|
+
addButtons.push({ button: btn, home: addRowGroup, group: 'rows' });
|
|
1068
1108
|
|
|
1069
1109
|
btn.appendChild(dom.create('<i class="bi bi-plus"></i>'));
|
|
1070
1110
|
|
|
@@ -1079,7 +1119,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1079
1119
|
btn.appendChild(dom.create(icon));
|
|
1080
1120
|
});
|
|
1081
1121
|
|
|
1082
|
-
|
|
1122
|
+
start.appendChild(addContainerGroup);
|
|
1083
1123
|
|
|
1084
1124
|
// A container starts in a row of its own, the way the add row
|
|
1085
1125
|
// buttons next to these ones do
|
|
@@ -1101,6 +1141,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1101
1141
|
}, { parent: canvas, source: 'tool' });
|
|
1102
1142
|
});
|
|
1103
1143
|
addContainerGroup.appendChild(button);
|
|
1144
|
+
addButtons.push({ button: button, home: addContainerGroup, group: toolbarGroupOf(definition.group) || 'content' });
|
|
1104
1145
|
});
|
|
1105
1146
|
|
|
1106
1147
|
// A feature plugin's own buttons, beside the containers': what
|
|
@@ -1116,6 +1157,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1116
1157
|
endItems.push(button);
|
|
1117
1158
|
} else {
|
|
1118
1159
|
addContainerGroup.appendChild(button);
|
|
1160
|
+
// One that names no category of its own has its plugin's
|
|
1161
|
+
addButtons.push({ button: button, home: addContainerGroup, group: toolbarGroupOf(item.group) || name });
|
|
1119
1162
|
}
|
|
1120
1163
|
});
|
|
1121
1164
|
});
|
|
@@ -1186,9 +1229,194 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1186
1229
|
endItems.forEach(function(button) { end.appendChild(button); });
|
|
1187
1230
|
}
|
|
1188
1231
|
|
|
1232
|
+
if (settings.toolbar_groups) { createGroupTabs(start); }
|
|
1233
|
+
if (overflowMenu) { createOverflowMenu(start); }
|
|
1234
|
+
|
|
1189
1235
|
makeToolbarDraggable();
|
|
1190
1236
|
}
|
|
1191
1237
|
|
|
1238
|
+
/** The core's categories, ahead of the plugins' own. */
|
|
1239
|
+
var CORE_GROUPS = [
|
|
1240
|
+
{ name: 'rows', labelKey: 'group.rows' },
|
|
1241
|
+
{ name: 'content', labelKey: 'group.content' },
|
|
1242
|
+
];
|
|
1243
|
+
|
|
1244
|
+
/** A plugin's category, when it names one. */
|
|
1245
|
+
function toolbarGroupOf(value) {
|
|
1246
|
+
return typeof value === 'string' && value.trim() !== '' ? value : null;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* toolbar_groups: tabs at the start of the toolbar, one per category
|
|
1251
|
+
* of add buttons, and only the chosen one's buttons on it. The rest
|
|
1252
|
+
* wait in a hidden stash inside the toolbar, so toolbarItems still
|
|
1253
|
+
* finds them and the btn-groups round the corners of what shows. With
|
|
1254
|
+
* a single category there is nothing to choose, and no tabs.
|
|
1255
|
+
*/
|
|
1256
|
+
function createGroupTabs(start) {
|
|
1257
|
+
// The core's two first, then the rest as their first button comes
|
|
1258
|
+
var names = CORE_GROUPS.map(function(group) { return group.name; });
|
|
1259
|
+
addButtons.forEach(function(entry) {
|
|
1260
|
+
if (names.indexOf(entry.group) < 0) { names.push(entry.group); }
|
|
1261
|
+
});
|
|
1262
|
+
names = names.filter(function(name) {
|
|
1263
|
+
return addButtons.some(function(entry) { return entry.group === name; });
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
if (names.length < 2) { return; }
|
|
1267
|
+
|
|
1268
|
+
var tabs = dom.element('div', { 'class': 'ge-toolbar-groups btn-group', role: 'tablist', 'aria-label': t('group.select') });
|
|
1269
|
+
start.insertBefore(tabs, start.firstChild);
|
|
1270
|
+
var stash = mainControls.appendChild(dom.element('div', { 'class': 'ge-toolbar-stash', hidden: 'hidden' }));
|
|
1271
|
+
|
|
1272
|
+
names.forEach(function(name) {
|
|
1273
|
+
var tab = tabs.appendChild(dom.element('button', {
|
|
1274
|
+
type: 'button',
|
|
1275
|
+
'class': 'btn btn-sm btn-outline-primary',
|
|
1276
|
+
role: 'tab',
|
|
1277
|
+
'data-ge-group': name,
|
|
1278
|
+
}, groupLabel(name)));
|
|
1279
|
+
|
|
1280
|
+
tab.addEventListener('click', function() { showGroup(name); });
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
showGroup(names.indexOf(toolbarGroup) >= 0 ? toolbarGroup : names[0]);
|
|
1284
|
+
|
|
1285
|
+
function showGroup(name) {
|
|
1286
|
+
toolbarGroup = name;
|
|
1287
|
+
|
|
1288
|
+
addButtons.forEach(function(entry) {
|
|
1289
|
+
(entry.group === name ? entry.home : stash).appendChild(entry.button);
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
dom.children(tabs).forEach(function(tab) {
|
|
1293
|
+
var chosen = tab.getAttribute('data-ge-group') === name;
|
|
1294
|
+
dom.toggleClass(tab, 'active', chosen);
|
|
1295
|
+
tab.setAttribute('aria-selected', chosen ? 'true' : 'false');
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
refitToolbar();
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/** A category's label: its group.<name> string, or its first button's when it has none. */
|
|
1303
|
+
function groupLabel(name) {
|
|
1304
|
+
var key = 'group.' + name;
|
|
1305
|
+
if (hasString(settings, key)) { return t(key); }
|
|
1306
|
+
|
|
1307
|
+
var first = addButtons.filter(function(entry) { return entry.group === name; })[0];
|
|
1308
|
+
return first.button.getAttribute('title') || name;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/** The add buttons on the toolbar's line: all of them, or the chosen category's. */
|
|
1312
|
+
function shownAddButtons() {
|
|
1313
|
+
return addButtons.filter(function(entry) { return !dom.closest(entry.button, '.ge-toolbar-stash'); });
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/** The toolbar off the page, with what keeps its overflow menu up to date. */
|
|
1317
|
+
function removeMainControls() {
|
|
1318
|
+
if (toolbarLifetime) { toolbarLifetime.abort(); }
|
|
1319
|
+
toolbarLifetime = null;
|
|
1320
|
+
closeOverflowMenu = function() {};
|
|
1321
|
+
refitToolbar = function() {};
|
|
1322
|
+
mainControls.remove();
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* The add buttons that don't fit on the toolbar's line, behind a
|
|
1327
|
+
* button at its end. They are the same buttons, moved rather than
|
|
1328
|
+
* copied, so a click, a drag or a plugin finding them with
|
|
1329
|
+
* toolbarItems works as it does on the line. Which ones go is worked
|
|
1330
|
+
* out again whenever the toolbar or what is on it changes size.
|
|
1331
|
+
*/
|
|
1332
|
+
function createOverflowMenu(start) {
|
|
1333
|
+
var more = dom.create('<div class="ge-toolbar-more">' +
|
|
1334
|
+
'<button type="button" class="btn btn-sm btn-primary"><i class="bi bi-three-dots"></i></button>' +
|
|
1335
|
+
'<div class="ge-toolbar-overflow">' +
|
|
1336
|
+
'<div class="ge-addRowGroup btn-group"></div>' +
|
|
1337
|
+
'<div class="ge-addContainerGroup btn-group"></div>' +
|
|
1338
|
+
'</div>' +
|
|
1339
|
+
'</div>');
|
|
1340
|
+
start.parentNode.insertBefore(more, start.nextSibling);
|
|
1341
|
+
|
|
1342
|
+
var toggle = dom.one(more, 'button');
|
|
1343
|
+
var panel = dom.one(more, '.ge-toolbar-overflow');
|
|
1344
|
+
var spares = dom.all(panel, '.btn-group');
|
|
1345
|
+
// The spare group in the menu for each group on the line
|
|
1346
|
+
function spareOf(home) {
|
|
1347
|
+
return home === addRowGroup ? spares[0] : spares[1];
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
toggle.setAttribute('title', t('tool.more'));
|
|
1351
|
+
toggle.setAttribute('aria-haspopup', 'true');
|
|
1352
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
1353
|
+
|
|
1354
|
+
var menuLifetime = toolbarLifetime = new AbortController();
|
|
1355
|
+
var signal = { signal: menuLifetime.signal };
|
|
1356
|
+
|
|
1357
|
+
toggle.addEventListener('click', function() { openOverflow(!dom.hasClass(more, 'ge-open')); });
|
|
1358
|
+
// A button in the menu has done its job once it is clicked
|
|
1359
|
+
panel.addEventListener('click', function(e) {
|
|
1360
|
+
if (dom.closest(e.target, '[data-ge-toolbar]')) { openOverflow(false); }
|
|
1361
|
+
});
|
|
1362
|
+
document.addEventListener('pointerdown', function(e) {
|
|
1363
|
+
if (!more.contains(e.target)) { openOverflow(false); }
|
|
1364
|
+
}, signal);
|
|
1365
|
+
document.addEventListener('keydown', function(e) {
|
|
1366
|
+
if (e.key === 'Escape') { openOverflow(false); }
|
|
1367
|
+
}, signal);
|
|
1368
|
+
closeOverflowMenu = function() { openOverflow(false); };
|
|
1369
|
+
// A category chosen: another set of buttons to fit
|
|
1370
|
+
refitToolbar = fit;
|
|
1371
|
+
|
|
1372
|
+
fit();
|
|
1373
|
+
|
|
1374
|
+
if (window.ResizeObserver) {
|
|
1375
|
+
// Not in the callback itself: moving buttons there would
|
|
1376
|
+
// resize what is being observed before the frame is drawn
|
|
1377
|
+
var pending = false;
|
|
1378
|
+
var observer = new window.ResizeObserver(function() {
|
|
1379
|
+
if (pending) { return; }
|
|
1380
|
+
pending = true;
|
|
1381
|
+
window.requestAnimationFrame(function() {
|
|
1382
|
+
pending = false;
|
|
1383
|
+
if (!menuLifetime.signal.aborted) { fit(); }
|
|
1384
|
+
});
|
|
1385
|
+
});
|
|
1386
|
+
// The toolbar's width, and the buttons on the right of it
|
|
1387
|
+
// showing and hiding, as the paste button does
|
|
1388
|
+
observer.observe(wrapper);
|
|
1389
|
+
observer.observe(start);
|
|
1390
|
+
menuLifetime.signal.addEventListener('abort', function() { observer.disconnect(); });
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function openOverflow(open) {
|
|
1394
|
+
open = open && dom.hasClass(more, 'ge-needed');
|
|
1395
|
+
dom.toggleClass(more, 'ge-open', open);
|
|
1396
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/** Back on the line, then the last ones off it until the rest fit. */
|
|
1400
|
+
function fit() {
|
|
1401
|
+
// Only the chosen category's, with toolbar_groups: the others
|
|
1402
|
+
// wait in their stash
|
|
1403
|
+
var homes = shownAddButtons();
|
|
1404
|
+
homes.forEach(function(entry) { entry.home.appendChild(entry.button); });
|
|
1405
|
+
dom.removeClass(more, 'ge-needed');
|
|
1406
|
+
|
|
1407
|
+
if (start.scrollWidth <= start.clientWidth) {
|
|
1408
|
+
openOverflow(false);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
dom.addClass(more, 'ge-needed');
|
|
1413
|
+
for (var i = homes.length - 1; i >= 0 && start.scrollWidth > start.clientWidth; i--) {
|
|
1414
|
+
var into = spareOf(homes[i].home);
|
|
1415
|
+
into.insertBefore(homes[i].button, into.firstChild);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1192
1420
|
/**
|
|
1193
1421
|
* A toolbar button's face: the plugin's icon alone when it has one,
|
|
1194
1422
|
* with its label as the title, or a plus and the label otherwise.
|
|
@@ -1298,6 +1526,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1298
1526
|
|
|
1299
1527
|
helper = dom.addClass(button.cloneNode(true), 'ge-toolbar-helper');
|
|
1300
1528
|
document.body.appendChild(helper);
|
|
1529
|
+
// The menu a button came from would hide where it lands
|
|
1530
|
+
closeOverflowMenu();
|
|
1301
1531
|
dom.addClass(canvas, 'ge-dropping');
|
|
1302
1532
|
}
|
|
1303
1533
|
|
|
@@ -1913,7 +2143,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1913
2143
|
removeConfirmModal();
|
|
1914
2144
|
removeSettingsPanels();
|
|
1915
2145
|
removeDialog();
|
|
1916
|
-
|
|
2146
|
+
removeMainControls();
|
|
1917
2147
|
htmlTextArea.remove();
|
|
1918
2148
|
lifetime.abort();
|
|
1919
2149
|
instances.delete(canvas);
|
|
@@ -3214,6 +3444,127 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3214
3444
|
return createTool(drawer, t('tool.move'), 'ge-move', 'bi bi-arrows-move');
|
|
3215
3445
|
}
|
|
3216
3446
|
|
|
3447
|
+
/**
|
|
3448
|
+
* The drawers that span their node - a row's, a column's, a
|
|
3449
|
+
* container's, an element's, a section's - keep their tools to one
|
|
3450
|
+
* line. The tools that don't fit are hidden from the end, and a more
|
|
3451
|
+
* tool at the drawer's end unfolds it to show them all, wrapping as
|
|
3452
|
+
* it would otherwise. They stay where they are, so whatever finds a
|
|
3453
|
+
* tool in its drawer, or hangs a picker off it, still does.
|
|
3454
|
+
*
|
|
3455
|
+
* A text's drawer sits over the text, as wide as its tools, and a
|
|
3456
|
+
* pane's sits by its label: neither is one of them.
|
|
3457
|
+
*/
|
|
3458
|
+
var FITTED_DRAWERS = '.ge-tools-drawer:not(.ge-text-drawer):not(.ge-pane-drawer):not(.ge-code-inline)';
|
|
3459
|
+
|
|
3460
|
+
/**
|
|
3461
|
+
* Fit every drawer as it comes onto the canvas, and again when it
|
|
3462
|
+
* changes width or a tool is added to it, shown or hidden. Plugins
|
|
3463
|
+
* add tools at their own moments, so this watches rather than
|
|
3464
|
+
* waiting to be told.
|
|
3465
|
+
*/
|
|
3466
|
+
function watchDrawers() {
|
|
3467
|
+
if (settings.drawer_overflow === 'wrap' || !window.ResizeObserver || !window.MutationObserver) { return; }
|
|
3468
|
+
|
|
3469
|
+
var due = new Set();
|
|
3470
|
+
var frame = null;
|
|
3471
|
+
|
|
3472
|
+
// Once a frame, after the layout: moving tools in the observers'
|
|
3473
|
+
// callbacks would resize what they are watching
|
|
3474
|
+
function schedule(drawer) {
|
|
3475
|
+
due.add(drawer);
|
|
3476
|
+
if (frame === null) { frame = window.requestAnimationFrame(flush); }
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
function flush() {
|
|
3480
|
+
frame = null;
|
|
3481
|
+
var drawers = Array.from(due);
|
|
3482
|
+
due.clear();
|
|
3483
|
+
drawers.forEach(function(drawer) {
|
|
3484
|
+
if (!drawer.isConnected) {
|
|
3485
|
+
drawerResizes.unobserve(drawer);
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3488
|
+
fitDrawer(drawer);
|
|
3489
|
+
});
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
var drawerResizes = new window.ResizeObserver(function(entries) {
|
|
3493
|
+
entries.forEach(function(entry) { schedule(entry.target); });
|
|
3494
|
+
});
|
|
3495
|
+
|
|
3496
|
+
var mutations = new window.MutationObserver(function(records) {
|
|
3497
|
+
records.forEach(function(record) {
|
|
3498
|
+
var target = record.target;
|
|
3499
|
+
|
|
3500
|
+
// A tool shown or hidden
|
|
3501
|
+
if (record.type === 'attributes') {
|
|
3502
|
+
var parent = target.parentNode;
|
|
3503
|
+
if (parent && parent.nodeType === 1 && parent.matches(FITTED_DRAWERS)) { schedule(parent); }
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
// A tool added
|
|
3508
|
+
if (target.nodeType === 1 && target.matches(FITTED_DRAWERS)) {
|
|
3509
|
+
schedule(target);
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
record.addedNodes.forEach(function(node) {
|
|
3514
|
+
if (node.nodeType !== 1) { return; }
|
|
3515
|
+
dom.selfAndAll(node, FITTED_DRAWERS).forEach(function(drawer) {
|
|
3516
|
+
dom.addClass(drawer, 'ge-drawer-fit');
|
|
3517
|
+
drawerResizes.observe(drawer);
|
|
3518
|
+
schedule(drawer);
|
|
3519
|
+
});
|
|
3520
|
+
});
|
|
3521
|
+
});
|
|
3522
|
+
});
|
|
3523
|
+
mutations.observe(canvas, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
|
|
3524
|
+
|
|
3525
|
+
lifetime.signal.addEventListener('abort', function() {
|
|
3526
|
+
mutations.disconnect();
|
|
3527
|
+
drawerResizes.disconnect();
|
|
3528
|
+
if (frame !== null) { window.cancelAnimationFrame(frame); }
|
|
3529
|
+
});
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
/** Hide the tools that don't fit on the drawer's line, from the end, behind its more tool. */
|
|
3533
|
+
function fitDrawer(drawer) {
|
|
3534
|
+
var tools = dom.children(drawer, 'a').filter(function(tool) { return !dom.hasClass(tool, 'ge-drawer-more'); });
|
|
3535
|
+
var expanded = dom.hasClass(drawer, 'ge-drawer-expanded');
|
|
3536
|
+
|
|
3537
|
+
// Measured on one line, with every tool back on it
|
|
3538
|
+
dom.removeClass(drawer, 'ge-drawer-overflow ge-drawer-expanded');
|
|
3539
|
+
tools.forEach(function(tool) { dom.removeClass(tool, 'ge-tool-overflow'); });
|
|
3540
|
+
|
|
3541
|
+
if (!drawerOverflows(drawer, tools)) { return; }
|
|
3542
|
+
|
|
3543
|
+
dom.addClass(drawer, 'ge-drawer-overflow');
|
|
3544
|
+
if (!dom.child(drawer, '.ge-drawer-more')) {
|
|
3545
|
+
createTool(drawer, t('tool.more'), 'ge-drawer-more', 'bi bi-three-dots', function() {
|
|
3546
|
+
dom.toggleClass(drawer, 'ge-drawer-expanded');
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
for (var i = tools.length - 1; i >= 0 && drawerOverflows(drawer, tools); i--) {
|
|
3551
|
+
dom.addClass(tools[i], 'ge-tool-overflow');
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
dom.toggleClass(drawer, 'ge-drawer-expanded', expanded);
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
function drawerOverflows(drawer, tools) {
|
|
3558
|
+
var style = window.getComputedStyle(drawer);
|
|
3559
|
+
var limit = drawer.getBoundingClientRect().right -
|
|
3560
|
+
parseFloat(style.borderRightWidth) - parseFloat(style.paddingRight);
|
|
3561
|
+
|
|
3562
|
+
return tools.some(function(tool) {
|
|
3563
|
+
var rects = tool.getClientRects();
|
|
3564
|
+
return rects.length > 0 && rects[0].right > limit + 0.5;
|
|
3565
|
+
});
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3217
3568
|
/**
|
|
3218
3569
|
* A tool in a drawer: a link with an icon, and what it does. The
|
|
3219
3570
|
* handlers are a click handler, or { eventName: handler }; each gets
|
|
@@ -5416,6 +5767,10 @@ GridEditor.locales = {
|
|
|
5416
5767
|
'tool.indent_increase': 'Increase indent\n(hold shift for max)',
|
|
5417
5768
|
'tool.edit_source': 'Edit Source Code',
|
|
5418
5769
|
'tool.preview': 'Preview',
|
|
5770
|
+
'tool.more': 'More',
|
|
5771
|
+
'group.rows': 'Rows',
|
|
5772
|
+
'group.content': 'Content',
|
|
5773
|
+
'group.select': 'Add',
|
|
5419
5774
|
'panel.title': '{kind} settings',
|
|
5420
5775
|
'panel.close': 'Close',
|
|
5421
5776
|
'panel.done': 'Done',
|
|
@@ -51,6 +51,10 @@ GridEditor.locales.es = {
|
|
|
51
51
|
'tool.indent_increase': 'Aumentar sangría\n(Mayús para el máximo)',
|
|
52
52
|
'tool.edit_source': 'Editar el código fuente',
|
|
53
53
|
'tool.preview': 'Vista previa',
|
|
54
|
+
'tool.more': 'Más',
|
|
55
|
+
'group.rows': 'Filas',
|
|
56
|
+
'group.content': 'Contenido',
|
|
57
|
+
'group.select': 'Añadir',
|
|
54
58
|
'panel.title': 'Ajustes: {kind}',
|
|
55
59
|
'panel.close': 'Cerrar',
|
|
56
60
|
'panel.done': 'Hecho',
|
package/src/less/grideditor.less
CHANGED
|
@@ -67,6 +67,103 @@
|
|
|
67
67
|
margin-left: 5px;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/* toolbar_groups: the tabs choosing which category of add buttons
|
|
71
|
+
shows, at the start of the line. Never squeezed, and never behind the
|
|
72
|
+
more button */
|
|
73
|
+
.ge-toolbar-groups {
|
|
74
|
+
flex: none;
|
|
75
|
+
margin-right: 8px;
|
|
76
|
+
|
|
77
|
+
> .btn {
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* toolbar_overflow: 'menu'. The add buttons keep to one line, and the
|
|
83
|
+
ones that don't fit go behind a button at its end. The right-hand
|
|
84
|
+
buttons come before in the markup, so they are ordered back to how
|
|
85
|
+
floating them puts them. */
|
|
86
|
+
.ge-wrapper.ge-toolbar-menu {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: flex-start;
|
|
89
|
+
|
|
90
|
+
> .pull-right {
|
|
91
|
+
float: none;
|
|
92
|
+
}
|
|
93
|
+
> .ge-toolbar-end {
|
|
94
|
+
order: 1;
|
|
95
|
+
}
|
|
96
|
+
> .btn-group.pull-right {
|
|
97
|
+
order: 2;
|
|
98
|
+
}
|
|
99
|
+
> .ge-layout-mode {
|
|
100
|
+
order: 3;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ge-toolbar-start {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex: 1 1 auto;
|
|
107
|
+
min-width: 0;
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
|
|
110
|
+
/* A group as wide as its buttons and a button as wide as its label:
|
|
111
|
+
squeezed, they would wrap their labels rather than overflow */
|
|
112
|
+
> .btn-group {
|
|
113
|
+
flex: none;
|
|
114
|
+
}
|
|
115
|
+
.btn {
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ge-toolbar-more {
|
|
121
|
+
display: none;
|
|
122
|
+
position: relative;
|
|
123
|
+
flex: none;
|
|
124
|
+
margin: 0 5px 0 4px;
|
|
125
|
+
|
|
126
|
+
&.ge-needed {
|
|
127
|
+
display: block;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.ge-toolbar-overflow {
|
|
132
|
+
display: none;
|
|
133
|
+
position: absolute;
|
|
134
|
+
z-index: 30;
|
|
135
|
+
top: ~"calc(100% + 4px)";
|
|
136
|
+
right: 0;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: 6px;
|
|
139
|
+
width: max-content;
|
|
140
|
+
max-width: ~"min(420px, calc(100vw - 32px))";
|
|
141
|
+
padding: 6px;
|
|
142
|
+
background: #fff;
|
|
143
|
+
border: 1px solid rgba(0, 0, 0, .15);
|
|
144
|
+
border-radius: 6px;
|
|
145
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, .15);
|
|
146
|
+
|
|
147
|
+
> .btn-group {
|
|
148
|
+
flex-wrap: wrap;
|
|
149
|
+
gap: 4px;
|
|
150
|
+
margin: 0;
|
|
151
|
+
|
|
152
|
+
&:empty {
|
|
153
|
+
display: none;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/* Wrapped onto several lines, a group's buttons stand apart */
|
|
157
|
+
> .btn-group > .btn.btn {
|
|
158
|
+
margin-left: 0;
|
|
159
|
+
border-radius: 4px;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.ge-toolbar-more.ge-open > .ge-toolbar-overflow {
|
|
164
|
+
display: flex;
|
|
165
|
+
}
|
|
166
|
+
|
|
70
167
|
.ge-toolbar-end {
|
|
71
168
|
margin-right: 5px;
|
|
72
169
|
|
|
@@ -1038,6 +1135,43 @@
|
|
|
1038
1135
|
}
|
|
1039
1136
|
}
|
|
1040
1137
|
|
|
1138
|
+
/* drawer_overflow: 'menu'. A drawer keeps its tools to one line; the ones
|
|
1139
|
+
that don't fit are hidden behind a more tool at its end, which unfolds it
|
|
1140
|
+
to show them all */
|
|
1141
|
+
.ge-canvas.ge-editing .ge-tools-drawer.ge-drawer-fit {
|
|
1142
|
+
white-space: nowrap;
|
|
1143
|
+
|
|
1144
|
+
> .ge-details {
|
|
1145
|
+
white-space: normal;
|
|
1146
|
+
}
|
|
1147
|
+
> a.ge-tool-overflow,
|
|
1148
|
+
> a.ge-drawer-more {
|
|
1149
|
+
display: none;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
&.ge-drawer-overflow {
|
|
1153
|
+
padding-right: 26px;
|
|
1154
|
+
|
|
1155
|
+
> a.ge-drawer-more {
|
|
1156
|
+
display: inline-block;
|
|
1157
|
+
position: absolute;
|
|
1158
|
+
top: 0;
|
|
1159
|
+
right: 0;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
&.ge-drawer-expanded {
|
|
1164
|
+
white-space: normal;
|
|
1165
|
+
|
|
1166
|
+
> a.ge-tool-overflow {
|
|
1167
|
+
display: inline-block;
|
|
1168
|
+
}
|
|
1169
|
+
> a.ge-drawer-more {
|
|
1170
|
+
background: rgba(0, 0, 0, 0.08);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1041
1175
|
/* The handle belongs to editing, and never to the output */
|
|
1042
1176
|
.ge-canvas:not(.ge-editing) .ge-resize-handle {
|
|
1043
1177
|
display: none;
|
package/types/grideditor.d.ts
CHANGED
|
@@ -107,6 +107,12 @@ export interface GridEditorOptions {
|
|
|
107
107
|
text_tools?: HostTool[];
|
|
108
108
|
drag_handle?: 'tool' | 'drawer';
|
|
109
109
|
toolbar_drag?: 'auto' | boolean;
|
|
110
|
+
/** The add buttons that don't fit on the toolbar's line: behind a more button, or onto another line. Default 'menu'. */
|
|
111
|
+
toolbar_overflow?: 'menu' | 'wrap';
|
|
112
|
+
/** The drawers' tools that don't fit on one line: behind a more tool, or onto more lines. Default 'menu'. */
|
|
113
|
+
drawer_overflow?: 'menu' | 'wrap';
|
|
114
|
+
/** The add buttons in categories - rows, content, and a plugin's own - one shown at a time, chosen with tabs. Default false. */
|
|
115
|
+
toolbar_groups?: boolean;
|
|
110
116
|
/** A click in a column or a section makes it where the toolbar's buttons add. Default false. */
|
|
111
117
|
active_target?: boolean;
|
|
112
118
|
/** The plugins to use, of any kind; null is every one loaded. */
|