@themarioga/grid-editor 7.2.0 → 7.3.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 +41 -0
- package/README.md +83 -14
- package/UPGRADING.md +31 -0
- package/dist/grideditor.bundle.min.js +2 -2
- package/dist/grideditor.css +102 -0
- package/dist/grideditor.d.ts +18 -0
- package/dist/grideditor.esm.js +291 -31
- package/dist/grideditor.js +291 -31
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/grideditor.min.js +1 -1
- package/dist/grideditor.min.js.map +3 -3
- package/dist/locales/grideditor.es.esm.js +67 -0
- package/dist/locales/grideditor.es.js +67 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +2 -2
- package/dist/plugins/grideditor.float.esm.js +10 -1
- package/dist/plugins/grideditor.float.js +8 -2
- package/dist/plugins/grideditor.float.min.js +1 -1
- package/dist/plugins/grideditor.float.min.js.map +4 -4
- package/dist/plugins/grideditor.spacing.esm.js +12 -3
- package/dist/plugins/grideditor.spacing.js +9 -3
- package/dist/plugins/grideditor.spacing.min.js +1 -1
- package/dist/plugins/grideditor.spacing.min.js.map +4 -4
- package/dist/plugins/grideditor.style.esm.js +1593 -0
- package/dist/plugins/grideditor.style.js +1592 -0
- package/dist/plugins/grideditor.style.min.js +2 -0
- package/dist/plugins/grideditor.style.min.js.map +7 -0
- package/dist/plugins/grideditor.textalign.esm.js +10 -1
- package/dist/plugins/grideditor.textalign.js +8 -2
- package/dist/plugins/grideditor.textalign.min.js +1 -1
- package/dist/plugins/grideditor.textalign.min.js.map +4 -4
- package/dist/plugins/grideditor.visibility.esm.js +12 -3
- package/dist/plugins/grideditor.visibility.js +9 -3
- package/dist/plugins/grideditor.visibility.min.js +1 -1
- package/dist/plugins/grideditor.visibility.min.js.map +4 -4
- package/docs/locale-keys.md +86 -7
- package/docs/plugins.md +49 -5
- package/example/index.html +14 -0
- package/example/style.html +151 -0
- package/example/utilities.html +15 -11
- package/package.json +1 -1
- package/src/js/grideditor.js +365 -27
- package/src/js/locales/grideditor.es.js +67 -0
- package/src/js/plugins/grideditor.float.js +8 -33
- package/src/js/plugins/grideditor.spacing.js +8 -273
- package/src/js/plugins/grideditor.style.js +179 -0
- package/src/js/plugins/grideditor.textalign.js +8 -39
- package/src/js/plugins/grideditor.visibility.js +8 -147
- package/src/js/style/accordion.js +157 -0
- package/src/js/style/catalog.js +76 -0
- package/src/js/style/custom.js +84 -0
- package/src/js/style/fields.js +382 -0
- package/src/js/style/float.js +46 -0
- package/src/js/style/options.js +75 -0
- package/src/js/style/sections.js +271 -0
- package/src/js/style/spacing.js +286 -0
- package/src/js/style/textalign.js +52 -0
- package/src/js/style/visibility.js +160 -0
- package/src/less/grideditor.less +111 -0
- package/types/grideditor.d.ts +18 -0
package/example/utilities.html
CHANGED
|
@@ -14,18 +14,19 @@
|
|
|
14
14
|
<link rel="stylesheet" type="text/css" href="../dist/grideditor.css" />
|
|
15
15
|
|
|
16
16
|
<!-- The editor, and the utility plugins. Each is a file of its own, and
|
|
17
|
-
loading it is what puts its
|
|
17
|
+
loading it is what puts its fields in the settings panels. The style
|
|
18
|
+
plugin carries visibility, spacing, text alignment and float, which
|
|
19
|
+
were plugins of their own up to 7.2, and edits them in its Style
|
|
20
|
+
accordion; order, alignment and gutters are the grid's, and stay in
|
|
21
|
+
the Responsive section. -->
|
|
18
22
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
|
|
19
23
|
<script src="../dist/grideditor.min.js"></script>
|
|
20
|
-
<script src="../dist/plugins/grideditor.visibility.min.js"></script>
|
|
21
24
|
<script src="../dist/plugins/grideditor.order.min.js"></script>
|
|
22
25
|
<script src="../dist/plugins/grideditor.alignment.min.js"></script>
|
|
23
26
|
<script src="../dist/plugins/grideditor.gutters.min.js"></script>
|
|
24
|
-
<script src="../dist/plugins/grideditor.spacing.min.js"></script>
|
|
25
|
-
<script src="../dist/plugins/grideditor.textalign.min.js"></script>
|
|
26
27
|
<!-- Float works on elements, so the elements plugin comes first -->
|
|
27
28
|
<script src="../dist/plugins/grideditor.elements.min.js"></script>
|
|
28
|
-
<script src="../dist/plugins/grideditor.
|
|
29
|
+
<script src="../dist/plugins/grideditor.style.min.js"></script>
|
|
29
30
|
|
|
30
31
|
<script>
|
|
31
32
|
document.addEventListener('DOMContentLoaded', function() {
|
|
@@ -89,9 +90,11 @@
|
|
|
89
90
|
breakpoint. Pick a breakpoint in the dropdown and every
|
|
90
91
|
change is written for that breakpoint alone; in
|
|
91
92
|
<strong>All sizes</strong> it is written once, for every
|
|
92
|
-
size. Each drawer's gear has a <strong>
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
size. Each drawer's gear has a <strong>Style</strong>
|
|
94
|
+
accordion, whose sections hold visibility, spacing, text
|
|
95
|
+
alignment and float, and a <strong>Responsive</strong>
|
|
96
|
+
section for the grid's utilities. Every field says what
|
|
97
|
+
the breakpoint inherits and from where.
|
|
95
98
|
</p>
|
|
96
99
|
<div class="d-flex align-items-center gap-2 mb-3">
|
|
97
100
|
<label class="form-label mb-0" for="panel">A gear opens its settings in</label>
|
|
@@ -129,13 +132,14 @@
|
|
|
129
132
|
</p>
|
|
130
133
|
<p class="form-text">
|
|
131
134
|
<strong>Spacing:</strong> a padding and a margin group in
|
|
132
|
-
|
|
135
|
+
the Style accordion's Spacing section, each a side and a
|
|
136
|
+
value. The centred column
|
|
133
137
|
below gets more padding from small desktop up.
|
|
134
138
|
</p>
|
|
135
139
|
<p class="form-text">
|
|
136
|
-
<strong>Text alignment:</strong> a field in
|
|
140
|
+
<strong>Text alignment:</strong> a field in the Text section.
|
|
137
141
|
The centred column's text is centred from small desktop
|
|
138
|
-
up. <strong>Float:</strong> a field in an element's
|
|
142
|
+
up. <strong>Float:</strong> a field in an element's Position section;
|
|
139
143
|
the quote in the last row floats to the end from tablet up,
|
|
140
144
|
with the text flowing round it.
|
|
141
145
|
</p>
|
package/package.json
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"comment": "A scoped package publishes restricted unless it is told otherwise.",
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
|
-
"version": "7.
|
|
36
|
+
"version": "7.3.0",
|
|
37
37
|
"main": "dist/grideditor.js",
|
|
38
38
|
"module": "dist/grideditor.esm.js",
|
|
39
39
|
"types": "dist/grideditor.d.ts",
|
package/src/js/grideditor.js
CHANGED
|
@@ -475,6 +475,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
475
475
|
|
|
476
476
|
removeConfirmModal();
|
|
477
477
|
removeSettingsPanels();
|
|
478
|
+
removeDialog();
|
|
478
479
|
mainControls.remove();
|
|
479
480
|
createMainControls();
|
|
480
481
|
reset();
|
|
@@ -1753,6 +1754,10 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1753
1754
|
if (openSettingsState && !dom.attached(openSettingsState.node)) {
|
|
1754
1755
|
closeSettings();
|
|
1755
1756
|
}
|
|
1757
|
+
// And a dialog showing part of a panel that is gone
|
|
1758
|
+
if (dialogState && dialogState.home && !dom.attached(dialogState.home)) {
|
|
1759
|
+
closeDialog();
|
|
1760
|
+
}
|
|
1756
1761
|
|
|
1757
1762
|
runFilter(true);
|
|
1758
1763
|
dom.addClass(canvas, 'ge-editing');
|
|
@@ -1781,7 +1786,10 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1781
1786
|
plugins('onBeforeDeinit');
|
|
1782
1787
|
closeSizePicker();
|
|
1783
1788
|
hideDropMarker();
|
|
1789
|
+
// In case a dialog was opened with no settings panel open
|
|
1790
|
+
closeDialog();
|
|
1784
1791
|
dom.all(canvas, '.ge-tools-drawer').forEach(function(drawer) { drawer.remove(); });
|
|
1792
|
+
pluginFields.clear();
|
|
1785
1793
|
unwrapTexts();
|
|
1786
1794
|
plugins('onDeinit');
|
|
1787
1795
|
// After the rich text editors have let go of their content areas:
|
|
@@ -1836,6 +1844,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1836
1844
|
deinit();
|
|
1837
1845
|
removeConfirmModal();
|
|
1838
1846
|
removeSettingsPanels();
|
|
1847
|
+
removeDialog();
|
|
1839
1848
|
mainControls.remove();
|
|
1840
1849
|
htmlTextArea.remove();
|
|
1841
1850
|
lifetime.abort();
|
|
@@ -1857,9 +1866,36 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1857
1866
|
|
|
1858
1867
|
registerFamily('grid', {}, widthFamily());
|
|
1859
1868
|
if (settings.row_cols !== false) { registerFamily('grid', {}, rowColsFamily()); }
|
|
1860
|
-
|
|
1869
|
+
// A plugin whose factory says it `replaces` others has taken
|
|
1870
|
+
// their place: an old name in the plugins setting asks for it,
|
|
1871
|
+
// and the old plugin, loaded beside it, stands down
|
|
1872
|
+
var replacedBy = {};
|
|
1873
|
+
[GridEditor.containers, GridEditor.features, GridEditor.utilities].forEach(function(registry) {
|
|
1874
|
+
Object.keys(registry).forEach(function(name) {
|
|
1875
|
+
(registry[name].replaces || []).forEach(function(old) {
|
|
1876
|
+
if (!replacedBy[old]) { replacedBy[old] = name; }
|
|
1877
|
+
});
|
|
1878
|
+
});
|
|
1879
|
+
});
|
|
1880
|
+
var named = function(name) {
|
|
1861
1881
|
return !settings.plugins || settings.plugins.indexOf(name) !== -1;
|
|
1862
1882
|
};
|
|
1883
|
+
var namedAsOld = function(name) {
|
|
1884
|
+
return !!settings.plugins && settings.plugins.some(function(old) { return replacedBy[old] === name; });
|
|
1885
|
+
};
|
|
1886
|
+
var standsDown = function(name) {
|
|
1887
|
+
return !!replacedBy[name] && wanted(replacedBy[name]);
|
|
1888
|
+
};
|
|
1889
|
+
var wanted = function(name) {
|
|
1890
|
+
return !standsDown(name) && (named(name) || namedAsOld(name));
|
|
1891
|
+
};
|
|
1892
|
+
var replaced = function(name) {
|
|
1893
|
+
if (!standsDown(name) || !named(name)) { return false; }
|
|
1894
|
+
|
|
1895
|
+
warnOnceHere('replaced:' + name, 'the "' + name + '" plugin is part of "' + replacedBy[name] +
|
|
1896
|
+
'", which is loaded: ignoring it');
|
|
1897
|
+
return true;
|
|
1898
|
+
};
|
|
1863
1899
|
|
|
1864
1900
|
// What the plugins setting does not choose: the text editors,
|
|
1865
1901
|
// which content_types chooses. A page that names its containers
|
|
@@ -1869,16 +1905,16 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1869
1905
|
};
|
|
1870
1906
|
|
|
1871
1907
|
Object.keys(GridEditor.containers).forEach(function(type) {
|
|
1872
|
-
if (wanted(type)) { CONTAINERS[type] = GridEditor.containers[type](api); }
|
|
1908
|
+
if (!replaced(type) && wanted(type)) { CONTAINERS[type] = GridEditor.containers[type](api); }
|
|
1873
1909
|
});
|
|
1874
1910
|
|
|
1875
1911
|
Object.keys(GridEditor.features).forEach(function(name) {
|
|
1876
1912
|
var factory = GridEditor.features[name];
|
|
1877
|
-
if (featureWanted(name, factory)) { FEATURES[name] = factory(api); }
|
|
1913
|
+
if (!replaced(name) && featureWanted(name, factory)) { FEATURES[name] = factory(api); }
|
|
1878
1914
|
});
|
|
1879
1915
|
|
|
1880
1916
|
Object.keys(GridEditor.utilities).forEach(function(name) {
|
|
1881
|
-
if (!wanted(name)) { return; }
|
|
1917
|
+
if (replaced(name) || !wanted(name)) { return; }
|
|
1882
1918
|
|
|
1883
1919
|
UTILITIES[name] = GridEditor.utilities[name](api);
|
|
1884
1920
|
(UTILITIES[name].families || []).forEach(function(family) {
|
|
@@ -1896,6 +1932,12 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1896
1932
|
(settings.plugins || []).forEach(function(name) {
|
|
1897
1933
|
if (CONTAINERS[name] || FEATURES[name] || UTILITIES[name]) { return; }
|
|
1898
1934
|
|
|
1935
|
+
if (standsDown(name)) {
|
|
1936
|
+
warnOnceHere('renamed:' + name, 'the "' + name + '" plugin is part of "' + replacedBy[name] +
|
|
1937
|
+
'" now: name "' + replacedBy[name] + '" in the plugins setting instead');
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1899
1941
|
warnOnceHere('plugin:' + name, 'the "' + name + '" plugin is not loaded: ' +
|
|
1900
1942
|
'include dist/plugins/grideditor.' + name + '.js after the editor');
|
|
1901
1943
|
});
|
|
@@ -1948,6 +1990,12 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
1948
1990
|
setUtility: setUtility,
|
|
1949
1991
|
utilityField: utilityField,
|
|
1950
1992
|
bareStyle: bareStyle,
|
|
1993
|
+
// The host's own style, under the preview
|
|
1994
|
+
hostStyle: hostStyle,
|
|
1995
|
+
setHostStyle: setHostStyle,
|
|
1996
|
+
// A modal of the editor's own, over the settings
|
|
1997
|
+
openDialog: openDialog,
|
|
1998
|
+
closeDialog: closeDialog,
|
|
1951
1999
|
rowFromLayout: rowFromLayoutValue,
|
|
1952
2000
|
nodeHtml: nodeHtml,
|
|
1953
2001
|
// A text editor has rewritten a content area, so whatever the
|
|
@@ -2266,6 +2314,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2266
2314
|
if (classes) { classes.value = hostClasses(node).join(' '); }
|
|
2267
2315
|
dom.children(details, '.ge-utilities').forEach(function(section) { renderUtilities(section); });
|
|
2268
2316
|
}
|
|
2317
|
+
renderPluginFields(node);
|
|
2269
2318
|
refreshPreviews(node);
|
|
2270
2319
|
}
|
|
2271
2320
|
|
|
@@ -2362,6 +2411,84 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2362
2411
|
});
|
|
2363
2412
|
}
|
|
2364
2413
|
|
|
2414
|
+
/* The host's own style, under the preview */
|
|
2415
|
+
|
|
2416
|
+
/**
|
|
2417
|
+
* The node's style as the host wrote it: its declarations with every
|
|
2418
|
+
* property the preview set put back to what the preview recorded.
|
|
2419
|
+
* Held in a detached element's style, which parses and serializes it
|
|
2420
|
+
* the way the node's own would.
|
|
2421
|
+
*/
|
|
2422
|
+
function hostDeclaration(node) {
|
|
2423
|
+
var scratch = document.createElement('div');
|
|
2424
|
+
var was = {};
|
|
2425
|
+
|
|
2426
|
+
scratch.style.cssText = node.style.cssText;
|
|
2427
|
+
try { was = JSON.parse(node.getAttribute(PREVIEW_ATTR)) || {}; } catch (error) { /* no record */ }
|
|
2428
|
+
|
|
2429
|
+
Object.keys(was).forEach(function(property) {
|
|
2430
|
+
var before = was[property];
|
|
2431
|
+
|
|
2432
|
+
if (before && before[0]) {
|
|
2433
|
+
scratch.style.setProperty(property, before[0], before[1]);
|
|
2434
|
+
} else {
|
|
2435
|
+
scratch.style.removeProperty(property);
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
|
|
2439
|
+
return scratch.style;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
/**
|
|
2443
|
+
* ge.hostStyle(node, property): { value, priority } of one property
|
|
2444
|
+
* of the host's style, never the preview's. With no property, the
|
|
2445
|
+
* host's whole style as css text.
|
|
2446
|
+
*/
|
|
2447
|
+
function hostStyle(node, property) {
|
|
2448
|
+
var declaration = hostDeclaration(node);
|
|
2449
|
+
|
|
2450
|
+
if (property === undefined) { return declaration.cssText; }
|
|
2451
|
+
|
|
2452
|
+
return {
|
|
2453
|
+
value: declaration.getPropertyValue(property),
|
|
2454
|
+
priority: declaration.getPropertyPriority(property),
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
/**
|
|
2459
|
+
* ge.setHostStyle(node, property, value, priority): write one property
|
|
2460
|
+
* of the host's style, or take it off with an empty value. The
|
|
2461
|
+
* preview comes off the node while it is written and goes back on
|
|
2462
|
+
* after, so what the preview put back on deinit is the host's new
|
|
2463
|
+
* value. False, and nothing written, when the browser refuses the
|
|
2464
|
+
* value. `priority` left out keeps the one the property had.
|
|
2465
|
+
*/
|
|
2466
|
+
function setHostStyle(node, property, value, priority) {
|
|
2467
|
+
value = value === null || value === undefined ? '' : String(value).trim();
|
|
2468
|
+
|
|
2469
|
+
if (priority === undefined) { priority = hostStyle(node, property).priority; }
|
|
2470
|
+
|
|
2471
|
+
if (value !== '') {
|
|
2472
|
+
var check = document.createElement('div').style;
|
|
2473
|
+
check.setProperty(property, value, priority || '');
|
|
2474
|
+
if (check.getPropertyValue(property) === '') { return false; }
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
var previewed = node.hasAttribute(PREVIEW_ATTR);
|
|
2478
|
+
if (previewed) { clearPreviews(node); }
|
|
2479
|
+
|
|
2480
|
+
if (value === '') {
|
|
2481
|
+
node.style.removeProperty(property);
|
|
2482
|
+
} else {
|
|
2483
|
+
node.style.setProperty(property, value, priority || '');
|
|
2484
|
+
}
|
|
2485
|
+
dom.dropEmptyStyle(node);
|
|
2486
|
+
|
|
2487
|
+
if (previewed) { refreshPreviews(node); }
|
|
2488
|
+
|
|
2489
|
+
return true;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2365
2492
|
/* The panel */
|
|
2366
2493
|
|
|
2367
2494
|
/**
|
|
@@ -2434,10 +2561,32 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
2434
2561
|
|
|
2435
2562
|
var field = createField(node, family);
|
|
2436
2563
|
renderField(field, node);
|
|
2564
|
+
pluginFields.set(field, node);
|
|
2437
2565
|
|
|
2438
2566
|
return field;
|
|
2439
2567
|
}
|
|
2440
2568
|
|
|
2569
|
+
/**
|
|
2570
|
+
* The fields a plugin made, wherever it put them: a Responsive
|
|
2571
|
+
* section renders its own, and these are the rest - in a plugin's
|
|
2572
|
+
* section of the panel, or in the dialog. Each one's node's, or
|
|
2573
|
+
* every node's; a field no longer on the page is forgotten.
|
|
2574
|
+
*/
|
|
2575
|
+
var pluginFields = new Map(); // field -> the node it edits
|
|
2576
|
+
|
|
2577
|
+
function renderPluginFields(node) {
|
|
2578
|
+
pluginFields.forEach(function(fieldNode, field) {
|
|
2579
|
+
if (!field.isConnected) {
|
|
2580
|
+
pluginFields.delete(field);
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2583
|
+
if (node && fieldNode !== node) { return; }
|
|
2584
|
+
if (dom.closest(field, '.ge-utilities')) { return; }
|
|
2585
|
+
|
|
2586
|
+
renderField(field, fieldNode);
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2441
2590
|
/** Fill a section's fields, the families' and the plugins' own, for the view being edited. */
|
|
2442
2591
|
function renderUtilities(section) {
|
|
2443
2592
|
var node = sectionNodes.get(section);
|
|
@@ -3170,12 +3319,59 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3170
3319
|
classGroup.appendChild(btn);
|
|
3171
3320
|
});
|
|
3172
3321
|
|
|
3322
|
+
pluginSections(container).forEach(function(section) { detailsDiv.appendChild(section); });
|
|
3323
|
+
|
|
3173
3324
|
var utilities = createUtilitiesSection(container);
|
|
3174
3325
|
if (utilities) { detailsDiv.appendChild(utilities); }
|
|
3175
3326
|
|
|
3176
3327
|
return detailsDiv;
|
|
3177
3328
|
}
|
|
3178
3329
|
|
|
3330
|
+
/**
|
|
3331
|
+
* What plugins add to a node's panel, between its general fields and
|
|
3332
|
+
* the Responsive section: each plugin's panelSection(node, kind), in
|
|
3333
|
+
* the order they were registered. Where the panel has room - the
|
|
3334
|
+
* offcanvas, the modal - the section is in it. A popover or a panel
|
|
3335
|
+
* inline in the drawer gets a button instead, and the section opens
|
|
3336
|
+
* in the dialog.
|
|
3337
|
+
*/
|
|
3338
|
+
function pluginSections(node) {
|
|
3339
|
+
var kind = kindOf(node);
|
|
3340
|
+
var mode = panelMode();
|
|
3341
|
+
var sections = [];
|
|
3342
|
+
|
|
3343
|
+
[CONTAINERS, FEATURES, UTILITIES].forEach(function(registry) {
|
|
3344
|
+
Object.keys(registry).forEach(function(name) {
|
|
3345
|
+
var plugin = registry[name];
|
|
3346
|
+
var section = plugin.panelSection ? plugin.panelSection(node, kind) : null;
|
|
3347
|
+
if (!section || !section.body) { return; }
|
|
3348
|
+
|
|
3349
|
+
var holder = dom.element('div', { 'class': 'ge-panel-section', 'data-ge-plugin': name });
|
|
3350
|
+
|
|
3351
|
+
if (mode === 'offcanvas' || mode === 'modal') {
|
|
3352
|
+
holder.appendChild(section.body);
|
|
3353
|
+
} else {
|
|
3354
|
+
var label = t(section.labelKey);
|
|
3355
|
+
var open = holder.appendChild(dom.element('button', {
|
|
3356
|
+
type: 'button',
|
|
3357
|
+
'class': 'btn btn-sm btn-outline-secondary ge-panel-section-open',
|
|
3358
|
+
}, label));
|
|
3359
|
+
|
|
3360
|
+
dom.hide(section.body);
|
|
3361
|
+
holder.appendChild(section.body);
|
|
3362
|
+
open.addEventListener('click', function() {
|
|
3363
|
+
openDialog(section.titleKey ? t(section.titleKey, { kind: kindLabel(node) }) : label,
|
|
3364
|
+
section.body, open);
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
sections.push(holder);
|
|
3369
|
+
});
|
|
3370
|
+
});
|
|
3371
|
+
|
|
3372
|
+
return sections;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3179
3375
|
/* --------------------------------------------------------------
|
|
3180
3376
|
* Where a node's settings open.
|
|
3181
3377
|
*
|
|
@@ -3194,7 +3390,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3194
3390
|
var PANEL_MODES = ['offcanvas', 'popover', 'modal', 'inline'];
|
|
3195
3391
|
var settingsPanels = {}; // One of each, built on first use, outside the canvas
|
|
3196
3392
|
var openSettingsState = null; // What is open: { mode, node, details, home, next, gear }
|
|
3197
|
-
var modalState = new WeakMap(); // modal panel -> { watched, wanted, busy }
|
|
3393
|
+
var modalState = new WeakMap(); // modal panel -> { watched, wanted, busy, backdrop, dismissed }
|
|
3198
3394
|
|
|
3199
3395
|
function panelMode() {
|
|
3200
3396
|
if (PANEL_MODES.indexOf(settings.settings_panel) !== -1) { return settings.settings_panel; }
|
|
@@ -3251,7 +3447,8 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3251
3447
|
|
|
3252
3448
|
var signal = { signal: listening.signal };
|
|
3253
3449
|
document.addEventListener('keydown', function(e) {
|
|
3254
|
-
|
|
3450
|
+
// A dialog over the panel takes the Escape for itself
|
|
3451
|
+
if (e.key === 'Escape' && !dialogState) { closeSettings(); }
|
|
3255
3452
|
}, signal);
|
|
3256
3453
|
|
|
3257
3454
|
if (mode === 'offcanvas') {
|
|
@@ -3274,15 +3471,20 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3274
3471
|
}
|
|
3275
3472
|
// A press anywhere else puts it away, as a popover does
|
|
3276
3473
|
dom.on(document, 'mousedown touchstart', function(e) {
|
|
3277
|
-
if (!panel.contains(e.target) && !gear.contains(e.target)) { closeSettings(); }
|
|
3474
|
+
if (!dialogState && !panel.contains(e.target) && !gear.contains(e.target)) { closeSettings(); }
|
|
3278
3475
|
}, signal);
|
|
3279
3476
|
} else {
|
|
3280
|
-
showModal(panel)
|
|
3477
|
+
showModal(panel, function() {
|
|
3478
|
+
if (openSettingsState && openSettingsState.mode === 'modal') { closeSettings(); }
|
|
3479
|
+
});
|
|
3281
3480
|
}
|
|
3282
3481
|
}
|
|
3283
3482
|
|
|
3284
3483
|
/** Put what is open away, and its panel back in its drawer. */
|
|
3285
3484
|
function closeSettings() {
|
|
3485
|
+
// A dialog opened from the panel goes with it
|
|
3486
|
+
closeDialog();
|
|
3487
|
+
|
|
3286
3488
|
var open = openSettingsState;
|
|
3287
3489
|
if (!open) { return; }
|
|
3288
3490
|
openSettingsState = null;
|
|
@@ -3381,7 +3583,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3381
3583
|
settingsPanels[mode].remove();
|
|
3382
3584
|
}
|
|
3383
3585
|
});
|
|
3384
|
-
|
|
3586
|
+
Object.keys(settingsPanels).forEach(function(mode) { removeBackdrop(settingsPanels[mode]); });
|
|
3385
3587
|
settingsPanels = {};
|
|
3386
3588
|
}
|
|
3387
3589
|
|
|
@@ -3424,23 +3626,34 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3424
3626
|
});
|
|
3425
3627
|
}
|
|
3426
3628
|
|
|
3427
|
-
var settingsBackdrop = null; // The modal's backdrop, when Bootstrap's javascript is not there to make one
|
|
3428
|
-
|
|
3429
3629
|
/**
|
|
3430
3630
|
* Bootstrap's modal ignores a hide while it is still fading in, and a
|
|
3431
3631
|
* show while it is fading out, so the editor asks for what it wants
|
|
3432
3632
|
* and has it done once the running transition ends: a panel closed as
|
|
3433
3633
|
* it opens - getHtml, a second click - would otherwise stay open, and
|
|
3434
3634
|
* empty. A hide Bootstrap starts itself, from Escape or the backdrop,
|
|
3435
|
-
*
|
|
3635
|
+
* calls `dismissed`, as the close button does: the settings modal
|
|
3636
|
+
* closes the settings, the dialog closes the dialog.
|
|
3637
|
+
*
|
|
3638
|
+
* Without Bootstrap's javascript the modal is shown by hand, over a
|
|
3639
|
+
* backdrop of the editor's own that dismisses it when clicked.
|
|
3436
3640
|
*/
|
|
3437
|
-
function showModal(panel) {
|
|
3641
|
+
function showModal(panel, dismissed) {
|
|
3438
3642
|
var Modal = modalLibrary();
|
|
3643
|
+
var state = modalState.get(panel);
|
|
3644
|
+
|
|
3645
|
+
if (!state) {
|
|
3646
|
+
state = { wanted: null, busy: false, backdrop: null, dismissed: dismissed };
|
|
3647
|
+
modalState.set(panel, state);
|
|
3648
|
+
}
|
|
3649
|
+
state.dismissed = dismissed;
|
|
3439
3650
|
|
|
3440
3651
|
if (!Modal) {
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3652
|
+
if (!state.backdrop) {
|
|
3653
|
+
state.backdrop = dom.element('div', { 'class': 'modal-backdrop fade show ge-settings-backdrop' });
|
|
3654
|
+
state.backdrop.addEventListener('click', function() { state.dismissed(); });
|
|
3655
|
+
document.body.appendChild(state.backdrop);
|
|
3656
|
+
}
|
|
3444
3657
|
dom.addClass(panel, 'show');
|
|
3445
3658
|
panel.style.display = 'block';
|
|
3446
3659
|
panel.removeAttribute('aria-hidden');
|
|
@@ -3448,11 +3661,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3448
3661
|
}
|
|
3449
3662
|
|
|
3450
3663
|
var modal = Modal.getOrCreateInstance(panel);
|
|
3451
|
-
var state = modalState.get(panel);
|
|
3452
3664
|
|
|
3453
|
-
if (!state) {
|
|
3454
|
-
state =
|
|
3455
|
-
modalState.set(panel, state);
|
|
3665
|
+
if (!state.watched) {
|
|
3666
|
+
state.watched = true;
|
|
3456
3667
|
trackModal(panel);
|
|
3457
3668
|
|
|
3458
3669
|
panel.addEventListener('hide.bs.modal', function() {
|
|
@@ -3469,9 +3680,9 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3469
3680
|
panel.addEventListener('hidden.bs.modal', function() {
|
|
3470
3681
|
state.busy = false;
|
|
3471
3682
|
if (state.wanted === 'open') {
|
|
3472
|
-
showModal(panel);
|
|
3473
|
-
} else
|
|
3474
|
-
|
|
3683
|
+
showModal(panel, state.dismissed);
|
|
3684
|
+
} else {
|
|
3685
|
+
state.dismissed();
|
|
3475
3686
|
}
|
|
3476
3687
|
});
|
|
3477
3688
|
}
|
|
@@ -3485,18 +3696,19 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3485
3696
|
|
|
3486
3697
|
function hideModal(panel) {
|
|
3487
3698
|
var Modal = modalLibrary();
|
|
3699
|
+
var state = modalState.get(panel);
|
|
3700
|
+
|
|
3701
|
+
if (!state) { return; }
|
|
3702
|
+
state.wanted = 'closed';
|
|
3488
3703
|
|
|
3489
3704
|
if (!Modal) {
|
|
3490
|
-
if (
|
|
3705
|
+
if (state.backdrop) { state.backdrop.remove(); state.backdrop = null; }
|
|
3491
3706
|
dom.removeClass(panel, 'show');
|
|
3492
3707
|
panel.style.removeProperty('display');
|
|
3493
3708
|
panel.setAttribute('aria-hidden', 'true');
|
|
3494
3709
|
return;
|
|
3495
3710
|
}
|
|
3496
3711
|
|
|
3497
|
-
var state = modalState.get(panel) || { wanted: null, busy: false };
|
|
3498
|
-
state.wanted = 'closed';
|
|
3499
|
-
|
|
3500
3712
|
// Already out of sight - Bootstrap hid it itself, and this is its
|
|
3501
3713
|
// hidden event closing the settings - and nothing is under way:
|
|
3502
3714
|
// a hide now would be ignored, and no event would come to say so
|
|
@@ -3506,6 +3718,131 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
3506
3718
|
}
|
|
3507
3719
|
}
|
|
3508
3720
|
|
|
3721
|
+
/** A modal's hand made backdrop goes with it. */
|
|
3722
|
+
function removeBackdrop(panel) {
|
|
3723
|
+
var state = modalState.get(panel);
|
|
3724
|
+
if (state && state.backdrop) { state.backdrop.remove(); state.backdrop = null; }
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
/* --------------------------------------------------------------
|
|
3728
|
+
* The dialog: a modal of the editor's own, over whatever settings
|
|
3729
|
+
* panel is open, for what does not fit in one. A plugin's section
|
|
3730
|
+
* of the panel opens in it when the panel is a popover or inline,
|
|
3731
|
+
* and a plugin can open one itself through ge.openDialog.
|
|
3732
|
+
*
|
|
3733
|
+
* What it shows is borrowed: the body goes into the dialog when it
|
|
3734
|
+
* opens and back where it was when it closes, so everything that
|
|
3735
|
+
* finds it through its node keeps working. One per editor, built
|
|
3736
|
+
* on first use, outside the canvas.
|
|
3737
|
+
* -------------------------------------------------------------- */
|
|
3738
|
+
|
|
3739
|
+
var dialogPanel = null;
|
|
3740
|
+
var dialogState = null; // What is open: { body, home, next, opener, listening }
|
|
3741
|
+
|
|
3742
|
+
function dialog() {
|
|
3743
|
+
if (dialogPanel) { return dialogPanel; }
|
|
3744
|
+
|
|
3745
|
+
dialogPanel = dom.create('<div class="modal fade ge-dialog" tabindex="-1" role="dialog" aria-hidden="true">' +
|
|
3746
|
+
'<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">' +
|
|
3747
|
+
'<div class="modal-content">' +
|
|
3748
|
+
'<div class="modal-header"><h5 class="modal-title ge-dialog-title"></h5></div>' +
|
|
3749
|
+
'<div class="modal-body ge-dialog-body"></div>' +
|
|
3750
|
+
'<div class="modal-footer"></div>' +
|
|
3751
|
+
'</div>' +
|
|
3752
|
+
'</div>' +
|
|
3753
|
+
'</div>');
|
|
3754
|
+
dom.one(dialogPanel, '.modal-header').appendChild(dom.element('button', {
|
|
3755
|
+
type: 'button',
|
|
3756
|
+
'class': 'btn-close ge-dialog-close',
|
|
3757
|
+
'aria-label': t('panel.close'),
|
|
3758
|
+
}));
|
|
3759
|
+
dom.one(dialogPanel, '.modal-footer').appendChild(dom.element('button', {
|
|
3760
|
+
type: 'button',
|
|
3761
|
+
'class': 'btn btn-primary ge-dialog-close',
|
|
3762
|
+
}, t('panel.done')));
|
|
3763
|
+
|
|
3764
|
+
dom.delegate(dialogPanel, 'click', '.ge-dialog-close', function(e) {
|
|
3765
|
+
e.preventDefault();
|
|
3766
|
+
closeDialog();
|
|
3767
|
+
});
|
|
3768
|
+
|
|
3769
|
+
return document.body.appendChild(dialogPanel);
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
/**
|
|
3773
|
+
* ge.openDialog(title, body, opener): show `body` in the dialog. One
|
|
3774
|
+
* already open closes first. `opener`, when given, has the focus back
|
|
3775
|
+
* when it closes.
|
|
3776
|
+
*/
|
|
3777
|
+
function openDialog(title, body, opener) {
|
|
3778
|
+
closeDialog();
|
|
3779
|
+
|
|
3780
|
+
var panel = dialog();
|
|
3781
|
+
var listening = new AbortController();
|
|
3782
|
+
|
|
3783
|
+
dialogState = {
|
|
3784
|
+
body: body,
|
|
3785
|
+
home: body.parentElement,
|
|
3786
|
+
next: body.nextElementSibling,
|
|
3787
|
+
opener: opener || document.activeElement,
|
|
3788
|
+
listening: listening,
|
|
3789
|
+
};
|
|
3790
|
+
|
|
3791
|
+
dom.one(panel, '.ge-dialog-title').textContent = title;
|
|
3792
|
+
dom.one(panel, '.ge-dialog-body').appendChild(body);
|
|
3793
|
+
dom.show(body);
|
|
3794
|
+
|
|
3795
|
+
// Bootstrap's modal takes its own Escape; a hand made one does not
|
|
3796
|
+
if (!modalLibrary()) {
|
|
3797
|
+
document.addEventListener('keydown', function(e) {
|
|
3798
|
+
if (e.key === 'Escape') { closeDialog(); }
|
|
3799
|
+
}, { signal: listening.signal });
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
// Bootstrap puts a popover over a modal: the panel the dialog was
|
|
3803
|
+
// opened from goes under its backdrop while it is up
|
|
3804
|
+
Object.keys(settingsPanels).forEach(function(mode) {
|
|
3805
|
+
dom.addClass(settingsPanels[mode], 'ge-under-dialog');
|
|
3806
|
+
});
|
|
3807
|
+
|
|
3808
|
+
showModal(panel, function() { closeDialog(); });
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
/** ge.closeDialog(): put the dialog away, and what it showed back where it was. */
|
|
3812
|
+
function closeDialog() {
|
|
3813
|
+
var open = dialogState;
|
|
3814
|
+
if (!open) { return; }
|
|
3815
|
+
dialogState = null;
|
|
3816
|
+
|
|
3817
|
+
open.listening.abort();
|
|
3818
|
+
hideModal(dialogPanel);
|
|
3819
|
+
Object.keys(settingsPanels).forEach(function(mode) {
|
|
3820
|
+
dom.removeClass(settingsPanels[mode], 'ge-under-dialog');
|
|
3821
|
+
});
|
|
3822
|
+
|
|
3823
|
+
dom.hide(open.body);
|
|
3824
|
+
if (open.home && dom.attached(open.home)) {
|
|
3825
|
+
if (open.next && open.next.parentElement === open.home) {
|
|
3826
|
+
open.home.insertBefore(open.body, open.next);
|
|
3827
|
+
} else {
|
|
3828
|
+
open.home.appendChild(open.body);
|
|
3829
|
+
}
|
|
3830
|
+
} else {
|
|
3831
|
+
open.body.remove();
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
if (open.opener && dom.attached(open.opener) && open.opener.focus) { open.opener.focus(); }
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
function removeDialog() {
|
|
3838
|
+
closeDialog();
|
|
3839
|
+
if (!dialogPanel) { return; }
|
|
3840
|
+
|
|
3841
|
+
removeBackdrop(dialogPanel);
|
|
3842
|
+
retireModal(dialogPanel);
|
|
3843
|
+
dialogPanel = null;
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3509
3846
|
/** What a panel's title calls a node. */
|
|
3510
3847
|
function kindLabel(node) {
|
|
3511
3848
|
var kind = kindOf(node);
|
|
@@ -4835,6 +5172,7 @@ function build(instance, baseElem, optionsOrMethod) {
|
|
|
4835
5172
|
settingsScope().forEach(function(scope) {
|
|
4836
5173
|
dom.all(scope, '.ge-utilities').forEach(function(section) { renderUtilities(section); });
|
|
4837
5174
|
});
|
|
5175
|
+
renderPluginFields(null);
|
|
4838
5176
|
refreshPreviews(canvas);
|
|
4839
5177
|
plugins('onViewChange', key);
|
|
4840
5178
|
emit('view-change', { canvas: canvas, breakpoint: key, from: from, to: key });
|