@themarioga/grid-editor 8.8.1 → 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 +23 -0
- package/README.md +20 -1
- package/dist/grideditor.bundle.min.js +3 -3
- package/dist/grideditor.css +59 -5
- 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/dist/plugins/grideditor.carousel.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/js/plugins/grideditor.carousel.js +2 -1
- package/src/less/grideditor.less +67 -5
- package/types/grideditor.d.ts +28 -1
package/docs/plugins.md
CHANGED
|
@@ -141,7 +141,7 @@ element, and where there may be several, an array.
|
|
|
141
141
|
| `ge.createTool(drawer, title, className, iconClass, handlers)` | A tool in a drawer, which it returns. `handlers` is a click handler or `{ eventName: handler }`; each gets the DOM event, with `this` the tool |
|
|
142
142
|
| `ge.createMoveTool(drawer)` | The drag handle, unless `drag_handle` says the whole drawer is one; the tool, or null |
|
|
143
143
|
| `ge.addSettingsTool(drawer, node, presets)` | The gear, and the id and class panel it opens. Returns the panel, to add fields to |
|
|
144
|
-
| `ge.detailsOf(node)` | A node's settings panel, wherever it is: in its drawer, or open in the offcanvas, popover or
|
|
144
|
+
| `ge.detailsOf(node)` | A node's settings panel, wherever it is: in its drawer, or open in the offcanvas, popover, modal or sidebar `settings_panel` names, outside the canvas. Find a panel's fields through it, never through the drawer. Null for a node with none |
|
|
145
145
|
| `ge.deleteNode(kind, node, confirmText, animate)` | Remove a node: ask, animate, announce |
|
|
146
146
|
| `ge.place(node, kind, options)` | Put a created node where `appendTo` and friends say, through the add events. `options.source` is the payload's `source`, `api` by default |
|
|
147
147
|
| `ge.createPaneControls(pane, kind, hostTools, confirmText, remove)` | The drawer a pane gets: move, the host's tools, delete. Returns the drawer, for a plugin that wants other tools in it |
|
|
@@ -325,6 +325,9 @@ label instead, and `body` opens in the dialog, titled `titleKey` - with the
|
|
|
325
325
|
node's kind as `{kind}` - or the label. `body` stays the panel's either way,
|
|
326
326
|
so `ge.detailsOf(node)` finds what is in it.
|
|
327
327
|
|
|
328
|
+
With `settings_panel: 'sidebar'` the panel has room, as the offcanvas does:
|
|
329
|
+
the sections are in it, and `body` is in the sidebar while its node is selected.
|
|
330
|
+
|
|
328
331
|
A field made with `ge.utilityField` in a section follows the view and the
|
|
329
332
|
classes field as the Responsive section's do, wherever the section is at the
|
|
330
333
|
time. The inline-style plugin's accordion is a section, and what the spacing,
|
|
@@ -336,6 +339,17 @@ with `ge.hostStyle`: in a breakpoint view the node's `style` holds the
|
|
|
336
339
|
preview's `!important` values too, and they come off on `deinit` with
|
|
337
340
|
whatever was written under them.
|
|
338
341
|
|
|
342
|
+
### What the sidebar shows with nothing selected
|
|
343
|
+
|
|
344
|
+
**`sidebarEmpty(ge)`**, which any kind of plugin can have, returns what the
|
|
345
|
+
`'sidebar'` settings panel shows while nothing is selected -
|
|
346
|
+
`{ titleKey, body }`, a locale key for the title and an element - or null. It
|
|
347
|
+
is called each time the sidebar is left with nothing selected. The host's own
|
|
348
|
+
`sidebar.empty` goes first; then the first plugin, in the order they were
|
|
349
|
+
registered, that returns something; with none, the editor's message. A result
|
|
350
|
+
without a `body` element counts as null. `body` is taken out of the sidebar,
|
|
351
|
+
not destroyed, when a node is selected, so keep it and give it back.
|
|
352
|
+
|
|
339
353
|
### Settings of your own, saved on the node
|
|
340
354
|
|
|
341
355
|
A tool that opens a dialog and saves what the user chose on the node is a
|
package/example/utilities.html
CHANGED
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
default_view: 'xs',
|
|
44
44
|
|
|
45
45
|
// Where a node's settings open: 'offcanvas', the default,
|
|
46
|
-
// 'popover', 'modal',
|
|
46
|
+
// 'popover', 'modal', 'inline' in its drawer, or 'sidebar',
|
|
47
|
+
// a panel that stays and shows what is clicked
|
|
47
48
|
settings_panel: panel,
|
|
48
49
|
});
|
|
49
50
|
}
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
<option value="popover">a popover</option>
|
|
104
105
|
<option value="modal">a modal</option>
|
|
105
106
|
<option value="inline">the drawer itself (inline)</option>
|
|
107
|
+
<option value="sidebar">a sidebar that stays (click an element)</option>
|
|
106
108
|
</select>
|
|
107
109
|
</div>
|
|
108
110
|
<p class="form-text">
|
package/package.json
CHANGED
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',
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Only one slide shows on the canvas, as on the page. The container's drawer
|
|
13
13
|
* says which, and moves to the next and the previous; Bootstrap's own arrows
|
|
14
|
-
*
|
|
14
|
+
* are kept in the markup but not drawn, its indicators are drawn but do not
|
|
15
|
+
* answer, and neither does its javascript:
|
|
15
16
|
* the attributes it acts on are put aside while editing. A carousel's options
|
|
16
17
|
* - arrows, indicators, fade, dark theme, autoplay and its interval, pause,
|
|
17
18
|
* wrap, keyboard and touch - are Bootstrap's classes and attributes and
|