@themarioga/grid-editor 8.8.2 → 8.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +18 -0
- package/dist/grideditor.bundle.min.js +3 -3
- package/dist/grideditor.css +51 -0
- package/dist/grideditor.d.ts +28 -1
- package/dist/grideditor.esm.js +311 -20
- package/dist/grideditor.jquery.js +3 -1
- package/dist/grideditor.jquery.min.js +1 -1
- package/dist/grideditor.jquery.min.js.map +2 -2
- package/dist/grideditor.js +311 -20
- 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/docs/events.md +6 -0
- package/docs/locale-keys.md +4 -0
- package/docs/plugins.md +15 -1
- package/example/utilities.html +3 -1
- package/package.json +1 -1
- package/src/js/grideditor.jquery.js +2 -0
- package/src/js/grideditor.js +361 -29
- package/src/js/locales/grideditor.es.js +4 -0
- package/src/less/grideditor.less +59 -0
- package/types/grideditor.d.ts +28 -1
package/src/js/grideditor.js
CHANGED
|
@@ -38,6 +38,8 @@ var METHODS = {
|
|
|
38
38
|
setUtility: { value: true },
|
|
39
39
|
getActiveTarget: { value: true },
|
|
40
40
|
setActiveTarget: {},
|
|
41
|
+
getSelected: { value: true },
|
|
42
|
+
setSelected: {},
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -154,6 +156,9 @@ var NESTED_SETTINGS = {
|
|
|
154
156
|
indent: {
|
|
155
157
|
tools: true, // The indent tools in the column's drawer
|
|
156
158
|
},
|
|
159
|
+
sidebar: {
|
|
160
|
+
empty: null, // function(ge) -> { title, body } shown while nothing is selected, or null for the message
|
|
161
|
+
},
|
|
157
162
|
drag: {
|
|
158
163
|
delay: 0, // Milliseconds to hold before a drag starts
|
|
159
164
|
touch_delay: 100, // The same for touch, where 0 eats the page's scrolling
|
|
@@ -171,6 +176,13 @@ var editorCounter = 0;
|
|
|
171
176
|
/** The editor on each canvas element, so there is never a second one. */
|
|
172
177
|
var instances = new WeakMap();
|
|
173
178
|
|
|
179
|
+
/** The editor whose settings_panel is 'sidebar': a page has one sidebar, so one editor at most. */
|
|
180
|
+
var sidebarHolder = null;
|
|
181
|
+
|
|
182
|
+
/** The editor's own controls outside the canvas: a click on one is not a click on the page. */
|
|
183
|
+
var EDITOR_CONTROLS = '.ge-mainControls, .ge-settings-panel, .ge-sidebar-toggle, .ge-dialog, .ge-confirm, ' +
|
|
184
|
+
'.ge-size-picker, .modal, .modal-backdrop';
|
|
185
|
+
|
|
174
186
|
/**
|
|
175
187
|
* Translate one key.
|
|
176
188
|
*
|
|
@@ -408,7 +420,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
408
420
|
'locale_strings' : {}, // Overrides for individual keys
|
|
409
421
|
'callbacks' : {}, // before_*/after_* functions, the events by another route
|
|
410
422
|
'confirm_delete' : true, // Ask before deleting a row or a column
|
|
411
|
-
'settings_panel' : 'offcanvas', // Where a node's settings open: 'offcanvas', 'popover', 'modal' or '
|
|
423
|
+
'settings_panel' : 'offcanvas', // Where a node's settings open: 'offcanvas', 'popover', 'modal', 'inline' or 'sidebar'
|
|
424
|
+
'sidebar' : NESTED_SETTINGS.sidebar, // The 'sidebar' panel: what it shows while nothing is selected
|
|
412
425
|
'drag' : NESTED_SETTINGS.drag // How a drag behaves, whatever drives it
|
|
413
426
|
}, optionsOrMethod);
|
|
414
427
|
|
|
@@ -444,6 +457,19 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
444
457
|
var sortables = []; // Every list made sortable, so deinit destroys exactly those
|
|
445
458
|
var instanceId = ++editorCounter; // Scopes the sortable groups to this editor
|
|
446
459
|
var destroyed = false;
|
|
460
|
+
var selected = null; // The node whose settings the sidebar shows, with settings_panel 'sidebar'
|
|
461
|
+
var sidebarCollapsed = null; // Folded away, once the sidebar has been shown: the window's width decides the first time
|
|
462
|
+
var pluginHandle = null; // What the plugins were handed, for the hooks that call them back
|
|
463
|
+
|
|
464
|
+
if (settings.settings_panel === 'sidebar') {
|
|
465
|
+
if (sidebarHolder) {
|
|
466
|
+
warnOnceHere('sidebar', 'settings_panel "sidebar" is in use by another editor on the page, which has one ' +
|
|
467
|
+
'sidebar: using offcanvas');
|
|
468
|
+
settings.settings_panel = 'offcanvas';
|
|
469
|
+
} else {
|
|
470
|
+
sidebarHolder = instance;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
447
473
|
|
|
448
474
|
// What the editor remembers about the nodes it edits, which used to
|
|
449
475
|
// be jQuery data: a node's settings panel, a drag or a resize in
|
|
@@ -1055,10 +1081,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1055
1081
|
if (dom.hasClass(canvas, 'ge-editing')) { e.preventDefault(); }
|
|
1056
1082
|
}, signal);
|
|
1057
1083
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
// says nowhere, and the toolbar adds to the canvas again.
|
|
1084
|
+
var sidebarMode = panelMode() === 'sidebar';
|
|
1085
|
+
|
|
1086
|
+
if (settings.active_target || sidebarMode) {
|
|
1062
1087
|
// The click a drag ends in - a resize, a move - is not one:
|
|
1063
1088
|
// it comes where the pointer let go, not where it pressed
|
|
1064
1089
|
var pressed = null;
|
|
@@ -1070,28 +1095,67 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1070
1095
|
pressed = null;
|
|
1071
1096
|
if (!dom.hasClass(canvas, 'ge-editing')) { return; }
|
|
1072
1097
|
if (from && Math.abs(e.clientX - from.x) + Math.abs(e.clientY - from.y) > settings.drag.threshold) { return; }
|
|
1073
|
-
var region = dom.closest(e.target, targetSelector());
|
|
1074
1098
|
|
|
1075
|
-
|
|
1076
|
-
//
|
|
1077
|
-
|
|
1078
|
-
if (region && region === activeTarget &&
|
|
1079
|
-
(e.target === region || e.target === dom.child(region, '.ge-tools-drawer'))) {
|
|
1080
|
-
changeTarget(null);
|
|
1081
|
-
return;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
changeTarget(region && region !== canvas && canvas.contains(region) ? region : null);
|
|
1099
|
+
if (settings.active_target) { clickTarget(e); }
|
|
1100
|
+
// A tool that took its node off the canvas says nothing of where the click was
|
|
1101
|
+
if (sidebarMode && canvas.contains(e.target)) { changeSelection(settingsNodeAt(e.target)); }
|
|
1085
1102
|
}, signal);
|
|
1086
1103
|
|
|
1087
1104
|
// Escape is the panel's, the dialog's, and a field's or a
|
|
1088
|
-
// text's while one has the focus; after those, it is
|
|
1105
|
+
// text's while one has the focus; after those, it is the
|
|
1106
|
+
// selection's, and then the target's
|
|
1089
1107
|
document.addEventListener('keydown', function(e) {
|
|
1090
|
-
if (e.key !== 'Escape' ||
|
|
1108
|
+
if (e.key !== 'Escape' || openSettingsState || dialogState) { return; }
|
|
1109
|
+
if (confirmDialog && dom.hasClass(confirmDialog, 'show')) { return; }
|
|
1091
1110
|
var focus = document.activeElement;
|
|
1092
1111
|
if (focus && (dom.is(focus, 'input, textarea, select') || focus.isContentEditable)) { return; }
|
|
1093
|
-
|
|
1112
|
+
|
|
1113
|
+
if (selected && isEditing()) {
|
|
1114
|
+
changeSelection(null);
|
|
1115
|
+
} else if (activeTarget) {
|
|
1116
|
+
changeTarget(null);
|
|
1117
|
+
}
|
|
1118
|
+
}, signal);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (sidebarMode) {
|
|
1122
|
+
// The focus coming into a node selects it, as a click does
|
|
1123
|
+
canvas.addEventListener('focusin', function(e) {
|
|
1124
|
+
var node = isEditing() ? settingsNodeAt(e.target) : null;
|
|
1125
|
+
if (node) { changeSelection(node); }
|
|
1094
1126
|
}, signal);
|
|
1127
|
+
|
|
1128
|
+
// A click on the page, away from the canvas and the editor's own
|
|
1129
|
+
// controls, puts the selection away - not while a text is open,
|
|
1130
|
+
// whose editor has controls of its own out there
|
|
1131
|
+
document.addEventListener('click', function(e) {
|
|
1132
|
+
var target = e.target;
|
|
1133
|
+
if (!selected || !isEditing() || !target || target.nodeType !== 1) { return; }
|
|
1134
|
+
if (!dom.attached(target) || canvas.contains(target)) { return; }
|
|
1135
|
+
if (dom.closest(target, EDITOR_CONTROLS) || canvas.querySelector('.ge-rte-active')) { return; }
|
|
1136
|
+
|
|
1137
|
+
changeSelection(null);
|
|
1138
|
+
}, signal);
|
|
1139
|
+
|
|
1140
|
+
window.addEventListener('resize', reserveSidebarRoom, signal);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/* Whatever the click was for - a text, a tool - it also says
|
|
1144
|
+
where the toolbar adds next. A click on no column or region
|
|
1145
|
+
says nowhere, and the toolbar adds to the canvas again. */
|
|
1146
|
+
function clickTarget(e) {
|
|
1147
|
+
var region = dom.closest(e.target, targetSelector());
|
|
1148
|
+
|
|
1149
|
+
// The target's own background, or its drawer's, clicked
|
|
1150
|
+
// again takes it back. What is in it - a text, a tool, a
|
|
1151
|
+
// nested column - keeps it, or makes that the target
|
|
1152
|
+
if (region && region === activeTarget &&
|
|
1153
|
+
(e.target === region || e.target === dom.child(region, '.ge-tools-drawer'))) {
|
|
1154
|
+
changeTarget(null);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
changeTarget(region && region !== canvas && canvas.contains(region) ? region : null);
|
|
1095
1159
|
}
|
|
1096
1160
|
}
|
|
1097
1161
|
|
|
@@ -2074,6 +2138,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2074
2138
|
deinit();
|
|
2075
2139
|
// The canvas is made again from the html when it comes back
|
|
2076
2140
|
changeTarget(null);
|
|
2141
|
+
changeSelection(null);
|
|
2077
2142
|
htmlTextArea.style.height = (0.8 * document.documentElement.clientHeight) + 'px';
|
|
2078
2143
|
htmlTextArea.value = canvas.innerHTML;
|
|
2079
2144
|
dom.show(htmlTextArea);
|
|
@@ -2135,6 +2200,10 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2135
2200
|
if (openSettingsState && !dom.attached(openSettingsState.node)) {
|
|
2136
2201
|
closeSettings();
|
|
2137
2202
|
}
|
|
2203
|
+
// The same for the selected node, which the sidebar stops showing
|
|
2204
|
+
if (selected && !canvas.contains(selected)) {
|
|
2205
|
+
changeSelection(null);
|
|
2206
|
+
}
|
|
2138
2207
|
// And a dialog showing part of a panel that is gone
|
|
2139
2208
|
if (dialogState && dialogState.home && !dom.attached(dialogState.home)) {
|
|
2140
2209
|
closeDialog();
|
|
@@ -2163,11 +2232,14 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2163
2232
|
} else if (activeTarget) {
|
|
2164
2233
|
dom.addClass(activeTarget, 'ge-active-target');
|
|
2165
2234
|
}
|
|
2235
|
+
|
|
2236
|
+
showSidebar();
|
|
2166
2237
|
}
|
|
2167
2238
|
|
|
2168
2239
|
function deinit() {
|
|
2169
2240
|
// Its panel goes home to its drawer first, and both go together
|
|
2170
2241
|
closeSettings();
|
|
2242
|
+
hideSidebar();
|
|
2171
2243
|
dom.removeClass(canvas, 'ge-editing ge-drag-drawer ge-dropping');
|
|
2172
2244
|
// The mark goes, the target stays: getHtml comes straight back
|
|
2173
2245
|
if (activeTarget) { dom.removeClass(activeTarget, 'ge-active-target'); }
|
|
@@ -2376,6 +2448,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2376
2448
|
if (sourceOpen) { closeSource(); }
|
|
2377
2449
|
deinit();
|
|
2378
2450
|
activeTarget = null;
|
|
2451
|
+
selected = null;
|
|
2452
|
+
if (sidebarHolder === instance) { sidebarHolder = null; }
|
|
2379
2453
|
removeConfirmModal();
|
|
2380
2454
|
removeSettingsPanels();
|
|
2381
2455
|
removeDialog();
|
|
@@ -2396,7 +2470,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2396
2470
|
* something it can see.
|
|
2397
2471
|
*/
|
|
2398
2472
|
function loadPlugins() {
|
|
2399
|
-
var api = pluginApi();
|
|
2473
|
+
var api = pluginHandle = pluginApi();
|
|
2400
2474
|
|
|
2401
2475
|
registerFamily('grid', {}, widthFamily());
|
|
2402
2476
|
if (settings.row_cols !== false) { registerFamily('grid', {}, rowColsFamily()); }
|
|
@@ -3885,9 +3959,13 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3885
3959
|
var details = createDetails(node, presets || []);
|
|
3886
3960
|
detailsFor.set(node, details);
|
|
3887
3961
|
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3962
|
+
// The sidebar shows the panel of the node that is selected, so
|
|
3963
|
+
// there is no gear to open it with
|
|
3964
|
+
if (panelMode() !== 'sidebar') {
|
|
3965
|
+
createTool(drawer, t('tool.settings'), 'ge-settings', 'bi bi-gear-fill', function() {
|
|
3966
|
+
toggleSettings(node, details, this);
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3891
3969
|
|
|
3892
3970
|
// Beside the gear, because every node that has one is a node a
|
|
3893
3971
|
// utility may apply to, whichever plugin built its drawer. A
|
|
@@ -3999,7 +4077,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3999
4077
|
|
|
4000
4078
|
var holder = dom.element('div', { 'class': 'ge-panel-section', 'data-ge-plugin': name });
|
|
4001
4079
|
|
|
4002
|
-
if (mode === 'offcanvas' || mode === 'modal') {
|
|
4080
|
+
if (mode === 'offcanvas' || mode === 'modal' || mode === 'sidebar') {
|
|
4003
4081
|
holder.appendChild(sectionTitle(t(section.labelKey)));
|
|
4004
4082
|
holder.appendChild(section.body);
|
|
4005
4083
|
} else {
|
|
@@ -4039,7 +4117,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4039
4117
|
* not at all. The modal is Bootstrap's own when Bootstrap is there.
|
|
4040
4118
|
* -------------------------------------------------------------- */
|
|
4041
4119
|
|
|
4042
|
-
var PANEL_MODES = ['offcanvas', 'popover', 'modal', 'inline'];
|
|
4120
|
+
var PANEL_MODES = ['offcanvas', 'popover', 'modal', 'inline', 'sidebar'];
|
|
4043
4121
|
var settingsPanels = {}; // One of each, built on first use, outside the canvas
|
|
4044
4122
|
var openSettingsState = null; // What is open: { mode, node, details, home, next, gear }
|
|
4045
4123
|
var modalState = new WeakMap(); // modal panel -> { watched, wanted, busy, backdrop, dismissed }
|
|
@@ -4059,9 +4137,12 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4059
4137
|
|
|
4060
4138
|
/** Where settings fields are found: the canvas, and the panel open outside it. */
|
|
4061
4139
|
function settingsScope() {
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4140
|
+
var scope = [canvas];
|
|
4141
|
+
|
|
4142
|
+
if (openSettingsState && openSettingsState.mode !== 'inline') { scope.push(openSettingsState.details); }
|
|
4143
|
+
if (sidebarShown) { scope.push(sidebarShown.details); }
|
|
4144
|
+
|
|
4145
|
+
return scope;
|
|
4065
4146
|
}
|
|
4066
4147
|
|
|
4067
4148
|
/** The gear: its node's panel, opened or closed; another node's closes first. */
|
|
@@ -4155,6 +4236,11 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4155
4236
|
hideModal(panel);
|
|
4156
4237
|
}
|
|
4157
4238
|
|
|
4239
|
+
returnPanel(open);
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
/** A node's panel back where it was in its drawer, and the node no longer marked. */
|
|
4243
|
+
function returnPanel(open) {
|
|
4158
4244
|
dom.removeClass(open.node, 'ge-settings-target');
|
|
4159
4245
|
open.details.style.removeProperty('display');
|
|
4160
4246
|
dom.dropEmptyStyle(open.details);
|
|
@@ -4199,6 +4285,15 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4199
4285
|
'</div>');
|
|
4200
4286
|
dom.one(panel, '.popover-header').appendChild(closeButton());
|
|
4201
4287
|
dom.hide(panel);
|
|
4288
|
+
} else if (mode === 'sidebar') {
|
|
4289
|
+
panel = dom.create('<div class="offcanvas offcanvas-end ge-settings-panel ge-settings-sidebar" tabindex="-1" role="complementary">' +
|
|
4290
|
+
'<div class="offcanvas-header"><h5 class="offcanvas-title ge-settings-title"></h5></div>' +
|
|
4291
|
+
'<div class="offcanvas-body ge-settings-body"></div>' +
|
|
4292
|
+
'</div>');
|
|
4293
|
+
panel.id = 'ge-settings-sidebar-' + instanceId;
|
|
4294
|
+
dom.one(panel, '.offcanvas-header').appendChild(closeButton());
|
|
4295
|
+
// Beside the panel rather than in it: it stays on the window's edge when the panel is folded away
|
|
4296
|
+
settingsPanels['sidebar-toggle'] = document.body.appendChild(sidebarToggle(panel));
|
|
4202
4297
|
} else {
|
|
4203
4298
|
panel = dom.create('<div class="modal fade ge-settings-panel ge-settings-modal" tabindex="-1" role="dialog" aria-hidden="true">' +
|
|
4204
4299
|
'<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">' +
|
|
@@ -4218,7 +4313,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4218
4313
|
|
|
4219
4314
|
dom.delegate(panel, 'click', '.ge-settings-close', function(e) {
|
|
4220
4315
|
e.preventDefault();
|
|
4221
|
-
closeSettings();
|
|
4316
|
+
if (mode === 'sidebar') { changeSelection(null); } else { closeSettings(); }
|
|
4222
4317
|
});
|
|
4223
4318
|
|
|
4224
4319
|
settingsPanels[mode] = document.body.appendChild(panel);
|
|
@@ -4227,6 +4322,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4227
4322
|
|
|
4228
4323
|
function removeSettingsPanels() {
|
|
4229
4324
|
closeSettings();
|
|
4325
|
+
unshowSidebar();
|
|
4326
|
+
dom.removeClass(document.documentElement, 'ge-sidebar-open');
|
|
4230
4327
|
|
|
4231
4328
|
Object.keys(settingsPanels).forEach(function(mode) {
|
|
4232
4329
|
if (mode === 'modal') {
|
|
@@ -4239,6 +4336,218 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4239
4336
|
settingsPanels = {};
|
|
4240
4337
|
}
|
|
4241
4338
|
|
|
4339
|
+
/* --------------------------------------------------------------
|
|
4340
|
+
* settings_panel: 'sidebar'. An offcanvas that stays: there while
|
|
4341
|
+
* the canvas is edited, making room for itself on the page, with the
|
|
4342
|
+
* settings of the node that is selected in it - or, with none, a
|
|
4343
|
+
* message, or what the host or a plugin put there instead.
|
|
4344
|
+
*
|
|
4345
|
+
* The panels are the same ones: the selected node's moves into the
|
|
4346
|
+
* sidebar and goes back to its drawer when another is selected, so
|
|
4347
|
+
* ge.detailsOf(node) finds it either way. Folded away, the panel
|
|
4348
|
+
* slides out and its button stays on the window's edge.
|
|
4349
|
+
* -------------------------------------------------------------- */
|
|
4350
|
+
|
|
4351
|
+
var SIDEBAR_MIN_WIDTH = 576; // Bootstrap's sm: narrower than that, the sidebar covers the page instead of making room
|
|
4352
|
+
var sidebarShown = null; // The selected node's panel while it is in the sidebar: { node, details, home, next }
|
|
4353
|
+
var sidebarEmpty = null; // What the sidebar shows while nothing is selected: { body }
|
|
4354
|
+
|
|
4355
|
+
function isEditing() {
|
|
4356
|
+
return dom.hasClass(canvas, 'ge-editing');
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
function sidebarToggle(panel) {
|
|
4360
|
+
var button = dom.element('button', { type: 'button', 'class': 'ge-sidebar-toggle', 'aria-controls': panel.id });
|
|
4361
|
+
button.appendChild(dom.element('i'));
|
|
4362
|
+
button.addEventListener('click', function() { foldSidebar(!sidebarCollapsed); });
|
|
4363
|
+
return button;
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
function foldSidebar(collapsed) {
|
|
4367
|
+
sidebarCollapsed = collapsed;
|
|
4368
|
+
applySidebar();
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
/** The panel and its button as `sidebarCollapsed` says. */
|
|
4372
|
+
function applySidebar() {
|
|
4373
|
+
var panel = settingsPanels.sidebar;
|
|
4374
|
+
var toggle = settingsPanels['sidebar-toggle'];
|
|
4375
|
+
if (!panel) { return; }
|
|
4376
|
+
|
|
4377
|
+
var open = !sidebarCollapsed;
|
|
4378
|
+
|
|
4379
|
+
if (open && !dom.hasClass(panel, 'show')) {
|
|
4380
|
+
dom.removeClass(panel, 'hiding');
|
|
4381
|
+
panel.getBoundingClientRect(); // So the slide in is a transition, not a jump
|
|
4382
|
+
dom.addClass(panel, 'show');
|
|
4383
|
+
} else if (!open && dom.hasClass(panel, 'show')) {
|
|
4384
|
+
dom.removeClass(panel, 'show');
|
|
4385
|
+
dom.addClass(panel, 'hiding');
|
|
4386
|
+
window.setTimeout(function() { dom.removeClass(panel, 'hiding'); }, 300);
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
var label = open ? t('panel.collapse') : t('panel.expand');
|
|
4390
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
4391
|
+
toggle.setAttribute('aria-label', label);
|
|
4392
|
+
toggle.setAttribute('title', label);
|
|
4393
|
+
dom.one(toggle, 'i').className = 'bi ' + (open ? 'bi-chevron-right' : 'bi-chevron-left');
|
|
4394
|
+
|
|
4395
|
+
reserveSidebarRoom();
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
/** The page makes room for the sidebar only while it is edited, unfolded, and the window is wide enough to have room. */
|
|
4399
|
+
function reserveSidebarRoom() {
|
|
4400
|
+
dom.toggleClass(document.documentElement, 'ge-sidebar-open',
|
|
4401
|
+
!!settingsPanels.sidebar && isEditing() && !sidebarCollapsed && window.innerWidth >= SIDEBAR_MIN_WIDTH);
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
/** Editing starts, or starts again: the sidebar is there, showing what is selected. */
|
|
4405
|
+
function showSidebar() {
|
|
4406
|
+
if (panelMode() !== 'sidebar') { return; }
|
|
4407
|
+
|
|
4408
|
+
var panel = settingsPanel('sidebar');
|
|
4409
|
+
if (sidebarCollapsed === null) { sidebarCollapsed = window.innerWidth < SIDEBAR_MIN_WIDTH; }
|
|
4410
|
+
|
|
4411
|
+
panel.style.removeProperty('display');
|
|
4412
|
+
settingsPanels['sidebar-toggle'].style.removeProperty('display');
|
|
4413
|
+
renderSidebar();
|
|
4414
|
+
applySidebar();
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
/** Editing stops: out of sight, with the selected node's panel back in its drawer. The selection stays. */
|
|
4418
|
+
function hideSidebar() {
|
|
4419
|
+
unshowDetails();
|
|
4420
|
+
|
|
4421
|
+
var panel = settingsPanels.sidebar;
|
|
4422
|
+
if (!panel) { return; }
|
|
4423
|
+
|
|
4424
|
+
panel.style.display = 'none';
|
|
4425
|
+
settingsPanels['sidebar-toggle'].style.display = 'none';
|
|
4426
|
+
dom.removeClass(document.documentElement, 'ge-sidebar-open');
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
function setSidebarTitle(text) {
|
|
4430
|
+
var panel = settingsPanels.sidebar;
|
|
4431
|
+
dom.one(panel, '.ge-settings-title').textContent = text;
|
|
4432
|
+
panel.setAttribute('aria-label', text);
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
function unshowDetails() {
|
|
4436
|
+
var shown = sidebarShown;
|
|
4437
|
+
sidebarShown = null;
|
|
4438
|
+
if (shown) { returnPanel(shown); }
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
/** Whatever the sidebar holds out of it: the panel to its drawer, the empty content out of the body, not destroyed. */
|
|
4442
|
+
function unshowSidebar() {
|
|
4443
|
+
unshowDetails();
|
|
4444
|
+
|
|
4445
|
+
if (sidebarEmpty) {
|
|
4446
|
+
sidebarEmpty.body.remove();
|
|
4447
|
+
sidebarEmpty = null;
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4451
|
+
/** What the sidebar shows is what is selected; nothing to do when it already does. */
|
|
4452
|
+
function renderSidebar() {
|
|
4453
|
+
var panel = settingsPanels.sidebar;
|
|
4454
|
+
if (!panel || !isEditing()) { return; }
|
|
4455
|
+
|
|
4456
|
+
var details = selected ? detailsOf(selected) : null;
|
|
4457
|
+
if (details ? sidebarShown && sidebarShown.details === details : !sidebarShown && sidebarEmpty) { return; }
|
|
4458
|
+
|
|
4459
|
+
unshowSidebar();
|
|
4460
|
+
var holder = dom.one(panel, '.ge-settings-body');
|
|
4461
|
+
|
|
4462
|
+
if (!details) {
|
|
4463
|
+
var content = emptyContent();
|
|
4464
|
+
var body = content ? content.body : dom.element('p', { 'class': 'ge-sidebar-empty' }, t('panel.sidebar_empty'));
|
|
4465
|
+
|
|
4466
|
+
setSidebarTitle(content ? content.title : t('panel.sidebar_title'));
|
|
4467
|
+
holder.appendChild(body);
|
|
4468
|
+
sidebarEmpty = { body: body };
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
sidebarShown = { node: selected, details: details, home: details.parentElement, next: details.nextElementSibling };
|
|
4473
|
+
setSidebarTitle(t('panel.title', { kind: kindLabel(selected) }));
|
|
4474
|
+
holder.appendChild(details);
|
|
4475
|
+
dom.show(details);
|
|
4476
|
+
dom.addClass(selected, 'ge-settings-target');
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
/**
|
|
4480
|
+
* What the sidebar shows with nothing selected, other than its
|
|
4481
|
+
* message: the host's sidebar.empty, else the first plugin's
|
|
4482
|
+
* sidebarEmpty, as { title, body }. Null for the message.
|
|
4483
|
+
*/
|
|
4484
|
+
function emptyContent() {
|
|
4485
|
+
var fromHost = settings.sidebar.empty;
|
|
4486
|
+
var found = null;
|
|
4487
|
+
|
|
4488
|
+
if (typeof fromHost === 'function') {
|
|
4489
|
+
found = emptyContentOf(fromHost(instance), function(result) { return result.title; });
|
|
4490
|
+
} else if (fromHost !== null && fromHost !== undefined) {
|
|
4491
|
+
warnOnceHere('sidebar.empty', 'sidebar.empty is a function(ge) returning { title, body }, not ' +
|
|
4492
|
+
typeof fromHost + ': ignored');
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
[CONTAINERS, FEATURES, UTILITIES].forEach(function(registry) {
|
|
4496
|
+
Object.keys(registry).forEach(function(name) {
|
|
4497
|
+
var plugin = registry[name];
|
|
4498
|
+
if (found || !plugin.sidebarEmpty) { return; }
|
|
4499
|
+
|
|
4500
|
+
found = emptyContentOf(plugin.sidebarEmpty(pluginHandle), function(result) {
|
|
4501
|
+
return typeof result.titleKey === 'string' && result.titleKey !== '' ? t(result.titleKey) : '';
|
|
4502
|
+
});
|
|
4503
|
+
});
|
|
4504
|
+
});
|
|
4505
|
+
|
|
4506
|
+
return found;
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
/** An answer of sidebar.empty or sidebarEmpty as { title, body }, or null when it has no body to show. */
|
|
4510
|
+
function emptyContentOf(result, titleOf) {
|
|
4511
|
+
if (!result || !result.body || result.body.nodeType !== 1) { return null; }
|
|
4512
|
+
|
|
4513
|
+
var title = titleOf(result);
|
|
4514
|
+
return { title: typeof title === 'string' && title !== '' ? title : t('panel.sidebar_title'), body: result.body };
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
/**
|
|
4518
|
+
* The node at `target` whose settings the sidebar shows: the
|
|
4519
|
+
* innermost one, its drawer included. A text's drawer is beside it
|
|
4520
|
+
* rather than in it.
|
|
4521
|
+
*/
|
|
4522
|
+
function settingsNodeAt(target) {
|
|
4523
|
+
var drawer = dom.closest(target, '.ge-tools-drawer', canvas);
|
|
4524
|
+
|
|
4525
|
+
if (drawer) {
|
|
4526
|
+
var home = drawer.parentElement;
|
|
4527
|
+
var owner = detailsFor.has(home) ? home : dom.children(home).filter(function(each) {
|
|
4528
|
+
return detailsFor.has(each);
|
|
4529
|
+
})[0];
|
|
4530
|
+
if (owner) { return owner; }
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
for (var node = target; node && node !== canvas; node = node.parentElement) {
|
|
4534
|
+
if (detailsFor.has(node)) { return node; }
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
return null;
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
/** The one way the selection changes, told only when it does. */
|
|
4541
|
+
function changeSelection(node) {
|
|
4542
|
+
if (node === selected) { return; }
|
|
4543
|
+
|
|
4544
|
+
var from = selected;
|
|
4545
|
+
selected = node;
|
|
4546
|
+
renderSidebar();
|
|
4547
|
+
|
|
4548
|
+
emit('selection-change', { canvas: canvas, node: node, from: from });
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4242
4551
|
/**
|
|
4243
4552
|
* Under the gear, or over it when there is more room there, inside
|
|
4244
4553
|
* the window, and no taller than the room it has: past that its body
|
|
@@ -5881,6 +6190,23 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
5881
6190
|
changeTarget(node);
|
|
5882
6191
|
}
|
|
5883
6192
|
|
|
6193
|
+
function apiSetSelected(value) {
|
|
6194
|
+
if (panelMode() !== 'sidebar') {
|
|
6195
|
+
warnOnceHere('sidebar-api', 'setSelected needs settings_panel "sidebar": without it, it does nothing');
|
|
6196
|
+
return;
|
|
6197
|
+
}
|
|
6198
|
+
if (value === null || value === undefined) { changeSelection(null); return; }
|
|
6199
|
+
|
|
6200
|
+
var node = nodeFrom(value);
|
|
6201
|
+
if (!node || node === canvas || !canvas.contains(node) || !detailsFor.has(node)) {
|
|
6202
|
+
var named = typeof value === 'string' ? value : (node ? '<' + node.tagName.toLowerCase() + '>' : String(value));
|
|
6203
|
+
warn('setSelected: ' + named + ' is not a node of the canvas with settings');
|
|
6204
|
+
return;
|
|
6205
|
+
}
|
|
6206
|
+
|
|
6207
|
+
changeSelection(node);
|
|
6208
|
+
}
|
|
6209
|
+
|
|
5884
6210
|
var own = {
|
|
5885
6211
|
getHtml: getHtml,
|
|
5886
6212
|
getPlainHtml: getPlainHtml,
|
|
@@ -5914,6 +6240,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
5914
6240
|
setUtility: setUtility,
|
|
5915
6241
|
getActiveTarget: function() { return settings.active_target ? activeTarget : null; },
|
|
5916
6242
|
setActiveTarget: apiSetActiveTarget,
|
|
6243
|
+
getSelected: function() { return panelMode() === 'sidebar' ? selected : null; },
|
|
6244
|
+
setSelected: apiSetSelected,
|
|
5917
6245
|
};
|
|
5918
6246
|
|
|
5919
6247
|
Object.keys(own).forEach(function(name) {
|
|
@@ -6071,6 +6399,10 @@ GridEditor.locales = {
|
|
|
6071
6399
|
'panel.title': '{kind} settings',
|
|
6072
6400
|
'panel.close': 'Close',
|
|
6073
6401
|
'panel.done': 'Done',
|
|
6402
|
+
'panel.sidebar_title': 'Settings',
|
|
6403
|
+
'panel.sidebar_empty': 'Click an element to see its settings',
|
|
6404
|
+
'panel.collapse': 'Hide settings',
|
|
6405
|
+
'panel.expand': 'Show settings',
|
|
6074
6406
|
'panel.id': 'Id',
|
|
6075
6407
|
'panel.classes': 'Classes',
|
|
6076
6408
|
'panel.section_general': 'Id and classes',
|
|
@@ -59,6 +59,10 @@ GridEditor.locales.es = {
|
|
|
59
59
|
'panel.title': 'Ajustes: {kind}',
|
|
60
60
|
'panel.close': 'Cerrar',
|
|
61
61
|
'panel.done': 'Hecho',
|
|
62
|
+
'panel.sidebar_title': 'Ajustes',
|
|
63
|
+
'panel.sidebar_empty': 'Haz clic en un elemento para ver sus ajustes',
|
|
64
|
+
'panel.collapse': 'Ocultar ajustes',
|
|
65
|
+
'panel.expand': 'Mostrar ajustes',
|
|
62
66
|
'panel.id': 'Id',
|
|
63
67
|
'panel.classes': 'Clases',
|
|
64
68
|
'panel.section_general': 'Id y clases',
|
package/src/less/grideditor.less
CHANGED
|
@@ -585,6 +585,65 @@
|
|
|
585
585
|
--bs-offcanvas-height: 60vh;
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
+
/* The sidebar: always there while editing, with the page making room for it
|
|
589
|
+
(html.ge-sidebar-open) where the window is wide enough. Narrower than
|
|
590
|
+
Bootstrap's sm it covers the page instead. */
|
|
591
|
+
.ge-settings-sidebar {
|
|
592
|
+
--bs-offcanvas-width: 340px;
|
|
593
|
+
|
|
594
|
+
.ge-sidebar-empty {
|
|
595
|
+
margin: 0;
|
|
596
|
+
color: var(--bs-secondary-color, #6c757d);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
html.ge-sidebar-open {
|
|
601
|
+
padding-right: 340px;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/* On the panel's left edge, centred; folded away, on the window's right edge.
|
|
605
|
+
Its own element, so the panel can slide out from under it. */
|
|
606
|
+
.ge-sidebar-toggle {
|
|
607
|
+
position: fixed;
|
|
608
|
+
top: 50%;
|
|
609
|
+
right: 340px;
|
|
610
|
+
z-index: 1045;
|
|
611
|
+
display: flex;
|
|
612
|
+
align-items: center;
|
|
613
|
+
justify-content: center;
|
|
614
|
+
width: 1.5rem;
|
|
615
|
+
height: 3rem;
|
|
616
|
+
padding: 0;
|
|
617
|
+
transform: translateY(-50%);
|
|
618
|
+
border: 1px solid var(--bs-border-color, #dee2e6);
|
|
619
|
+
border-right: 0;
|
|
620
|
+
border-radius: 0.375rem 0 0 0.375rem;
|
|
621
|
+
background: var(--bs-body-bg, #fff);
|
|
622
|
+
color: var(--bs-body-color, #212529);
|
|
623
|
+
transition: right 0.3s ease-in-out;
|
|
624
|
+
|
|
625
|
+
&[aria-expanded="false"] {
|
|
626
|
+
right: 0;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.ge-sidebar-toggle.ge-under-dialog {
|
|
631
|
+
z-index: 1040;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
@media (max-width: 575.98px) {
|
|
635
|
+
.ge-settings-sidebar {
|
|
636
|
+
--bs-offcanvas-width: 100vw;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.ge-sidebar-toggle[aria-expanded="true"] {
|
|
640
|
+
right: calc(100vw - 1.5rem);
|
|
641
|
+
border: 1px solid var(--bs-border-color, #dee2e6);
|
|
642
|
+
border-left: 0;
|
|
643
|
+
border-radius: 0 0.375rem 0.375rem 0;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
588
647
|
.ge-settings-popover {
|
|
589
648
|
/* Popper would position it; without Popper that is the editor's job */
|
|
590
649
|
position: absolute;
|