@themarioga/grid-editor 7.2.0 → 8.0.0-beta.2

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +96 -30
  3. package/UPGRADING.md +86 -0
  4. package/dist/grideditor.bundle.min.js +3 -3
  5. package/dist/grideditor.css +149 -6
  6. package/dist/grideditor.d.ts +18 -0
  7. package/dist/grideditor.esm.js +257 -48
  8. package/dist/grideditor.jquery.js +0 -13
  9. package/dist/grideditor.jquery.min.js +1 -1
  10. package/dist/grideditor.jquery.min.js.map +3 -3
  11. package/dist/grideditor.js +257 -48
  12. package/dist/grideditor.min.css +1 -1
  13. package/dist/grideditor.min.css.map +1 -1
  14. package/dist/grideditor.min.js +2 -2
  15. package/dist/grideditor.min.js.map +3 -3
  16. package/dist/locales/grideditor.es.esm.js +80 -6
  17. package/dist/locales/grideditor.es.js +80 -6
  18. package/dist/locales/grideditor.es.min.js +1 -1
  19. package/dist/locales/grideditor.es.min.js.map +2 -2
  20. package/dist/plugins/grideditor.accordion.min.js.map +2 -2
  21. package/dist/plugins/grideditor.alignment.esm.js +11 -38
  22. package/dist/plugins/grideditor.alignment.js +11 -38
  23. package/dist/plugins/grideditor.alignment.min.js +1 -1
  24. package/dist/plugins/grideditor.alignment.min.js.map +3 -3
  25. package/dist/plugins/grideditor.card.min.js.map +1 -1
  26. package/dist/plugins/grideditor.ckeditor.esm.js +0 -5
  27. package/dist/plugins/grideditor.ckeditor.js +0 -5
  28. package/dist/plugins/grideditor.ckeditor.min.js +1 -1
  29. package/dist/plugins/grideditor.ckeditor.min.js.map +3 -3
  30. package/dist/plugins/grideditor.clipboard.min.js.map +1 -1
  31. package/dist/plugins/grideditor.codemirror-inline.min.js.map +1 -1
  32. package/dist/plugins/grideditor.codemirror.min.js.map +1 -1
  33. package/dist/plugins/grideditor.elements.min.js.map +2 -2
  34. package/dist/plugins/grideditor.gutters.min.js.map +1 -1
  35. package/dist/plugins/grideditor.order.min.js.map +1 -1
  36. package/dist/plugins/grideditor.popup.min.js.map +1 -1
  37. package/dist/plugins/grideditor.sections.min.js.map +2 -2
  38. package/dist/plugins/grideditor.style.esm.js +1926 -0
  39. package/dist/plugins/grideditor.style.js +1923 -0
  40. package/dist/plugins/grideditor.style.min.js +4 -0
  41. package/dist/plugins/grideditor.style.min.js.map +7 -0
  42. package/dist/plugins/grideditor.summernote.min.js.map +2 -2
  43. package/dist/plugins/grideditor.tabs.min.js.map +2 -2
  44. package/dist/plugins/grideditor.tinymce.esm.js +1 -8
  45. package/dist/plugins/grideditor.tinymce.js +1 -8
  46. package/dist/plugins/grideditor.tinymce.min.js +1 -1
  47. package/dist/plugins/grideditor.tinymce.min.js.map +3 -3
  48. package/docs/events.md +4 -6
  49. package/docs/locale-keys.md +99 -15
  50. package/docs/plugins.md +47 -23
  51. package/example/ckeditor.html +0 -5
  52. package/example/index.html +17 -2
  53. package/example/style.html +163 -0
  54. package/example/utilities.html +21 -16
  55. package/package.json +1 -1
  56. package/src/js/dom.js +1 -2
  57. package/src/js/grideditor.jquery.js +7 -28
  58. package/src/js/grideditor.js +336 -67
  59. package/src/js/locales/grideditor.es.js +80 -6
  60. package/src/js/plugins/grideditor.alignment.js +21 -50
  61. package/src/js/plugins/grideditor.ckeditor.js +10 -23
  62. package/src/js/plugins/grideditor.style.js +185 -0
  63. package/src/js/plugins/grideditor.summernote.js +4 -5
  64. package/src/js/plugins/grideditor.tinymce.js +4 -17
  65. package/src/js/style/accordion.js +168 -0
  66. package/src/js/style/catalog.js +76 -0
  67. package/src/js/style/custom.js +84 -0
  68. package/src/js/style/display.js +229 -0
  69. package/src/js/style/drawerflow.js +118 -0
  70. package/src/js/style/fields.js +382 -0
  71. package/src/js/style/flex.js +156 -0
  72. package/src/js/{plugins/grideditor.float.js → style/float.js} +6 -16
  73. package/src/js/style/options.js +60 -0
  74. package/src/js/style/sections.js +298 -0
  75. package/src/js/{plugins/grideditor.spacing.js → style/spacing.js} +7 -19
  76. package/src/js/style/sticky.js +39 -0
  77. package/src/js/{plugins/grideditor.textalign.js → style/textalign.js} +6 -14
  78. package/src/js/text/grideditor.text.js +1 -1
  79. package/src/less/grideditor.less +151 -10
  80. package/types/grideditor.d.ts +18 -0
  81. package/dist/plugins/grideditor.float.esm.js +0 -34
  82. package/dist/plugins/grideditor.float.js +0 -41
  83. package/dist/plugins/grideditor.float.min.js +0 -1
  84. package/dist/plugins/grideditor.float.min.js.map +0 -7
  85. package/dist/plugins/grideditor.spacing.esm.js +0 -318
  86. package/dist/plugins/grideditor.spacing.js +0 -323
  87. package/dist/plugins/grideditor.spacing.min.js +0 -1
  88. package/dist/plugins/grideditor.spacing.min.js.map +0 -7
  89. package/dist/plugins/grideditor.textalign.esm.js +0 -40
  90. package/dist/plugins/grideditor.textalign.js +0 -47
  91. package/dist/plugins/grideditor.textalign.min.js +0 -1
  92. package/dist/plugins/grideditor.textalign.min.js.map +0 -7
  93. package/dist/plugins/grideditor.visibility.esm.js +0 -158
  94. package/dist/plugins/grideditor.visibility.js +0 -163
  95. package/dist/plugins/grideditor.visibility.min.js +0 -1
  96. package/dist/plugins/grideditor.visibility.min.js.map +0 -7
  97. package/src/js/plugins/grideditor.visibility.js +0 -171
@@ -0,0 +1,168 @@
1
+ /**
2
+ * The style plugin's accordion for one node: a section per group of
3
+ * properties, each with the merged utilities' per breakpoint fields, the
4
+ * inline fields and the catalog - as far as each applies to the node.
5
+ *
6
+ * Bootstrap's accordion markup, opened and closed here, so it needs none of
7
+ * Bootstrap's javascript. One section is open at a time, and the one the
8
+ * user opened last is open again on the next node's panel.
9
+ */
10
+ import * as dom from '../dom.js';
11
+ import { offeredOn } from './sections.js';
12
+ import { createStyleField, renderStyleField } from './fields.js';
13
+ import { createCatalog, renderCatalog } from './catalog.js';
14
+ import { createCustom } from './custom.js';
15
+
16
+ /** The kinds of node that take inline style: not a text, nor plain content. */
17
+ export function takesInlineStyle(node, kind) {
18
+ if (kind === 'row' || kind === 'column' || kind === 'element' || kind === 'section') { return true; }
19
+ if (dom.is(node, '[data-ge-container]')) { return true; }
20
+
21
+ // A pane of a container, whatever its container calls it
22
+ return !!dom.child(node, '.ge-tools-drawer.ge-pane-drawer');
23
+ }
24
+
25
+ /** Whether a merged family applies to the node, as the editor decides it. */
26
+ function familyApplies(family, node, kind) {
27
+ if (kind === 'plain') { kind = 'text'; }
28
+ if (family.appliesTo.indexOf(kind) !== -1) { return true; }
29
+
30
+ return family.appliesTo.indexOf('container') !== -1 && dom.is(node, '[data-ge-container]');
31
+ }
32
+
33
+ /**
34
+ * What a merged part shows in its section for a node: the spacing part
35
+ * its own panel, the others a field per family that applies to it - one,
36
+ * or flex's eleven, together.
37
+ */
38
+ function partContent(ge, part, node, kind) {
39
+ if (part.panel) { return part.panel(node, kind); }
40
+
41
+ var fields = part.families.filter(function(family) {
42
+ return familyApplies(family, node, kind);
43
+ }).map(function(family) {
44
+ return ge.utilityField(node, family.name);
45
+ });
46
+
47
+ if (fields.length < 2) { return fields[0] || null; }
48
+
49
+ var box = dom.element('div', { 'class': 'ge-style-part-fields' });
50
+ fields.forEach(function(field) { box.appendChild(field); });
51
+
52
+ return box;
53
+ }
54
+
55
+ /**
56
+ * `context` is the plugin's: its resolved sections, its parts by name, the
57
+ * state it keeps for the editor ({ open }) and `opened(key)`, called when
58
+ * the user opens or closes a section.
59
+ */
60
+ export function createAccordion(ge, node, kind, context) {
61
+ var inline = takesInlineStyle(node, kind);
62
+ var accordion = dom.element('div', { 'class': 'accordion ge-style' });
63
+ var fields = [];
64
+ var catalogs = [];
65
+ var notes = [];
66
+ var custom = null;
67
+ var customItem = null;
68
+ var changed = function() { render(); };
69
+
70
+ function item(key, labelKey) {
71
+ var entry = accordion.appendChild(dom.element('div', { 'class': 'accordion-item', 'data-ge-style-section': key }));
72
+ var header = entry.appendChild(dom.element('h2', { 'class': 'accordion-header' }));
73
+ var button = header.appendChild(dom.element('button', {
74
+ type: 'button',
75
+ 'class': 'accordion-button collapsed',
76
+ 'aria-expanded': 'false',
77
+ }, ge.t(labelKey)));
78
+ var collapse = entry.appendChild(dom.element('div', { 'class': 'accordion-collapse collapse' }));
79
+ var body = collapse.appendChild(dom.element('div', { 'class': 'accordion-body' }));
80
+
81
+ button.addEventListener('click', function() {
82
+ var opening = !dom.hasClass(collapse, 'show');
83
+ context.state.open = opening ? key : null;
84
+ // Every panel is built on init, so the others are told as well:
85
+ // whoever opened Border on one node wants it on the next
86
+ context.opened(context.state.open);
87
+ });
88
+
89
+ return { entry: entry, body: body };
90
+ }
91
+
92
+ function toggle(entry, shown) {
93
+ dom.toggleClass(dom.one(entry, '.accordion-collapse'), 'show', shown);
94
+ var button = dom.one(entry, '.accordion-button');
95
+ dom.toggleClass(button, 'collapsed', !shown);
96
+ button.setAttribute('aria-expanded', shown ? 'true' : 'false');
97
+ }
98
+
99
+ Object.keys(context.sections).forEach(function(key) {
100
+ var resolved = context.sections[key];
101
+ var section = resolved.section;
102
+
103
+ if (section.custom) {
104
+ if (inline) { customItem = item(key, section.labelKey); }
105
+ return;
106
+ }
107
+
108
+ var parts = section.parts.map(function(name) {
109
+ return context.parts[name] ? partContent(ge, context.parts[name], node, kind) : null;
110
+ }).filter(Boolean);
111
+ var properties = inline ? resolved.properties.filter(function(property) { return offeredOn(property, kind); }) : [];
112
+ var catalog = inline && resolved.catalog ? createCatalog(ge, node, kind, section.catalog) : null;
113
+
114
+ if (!parts.length && !properties.length && !catalog) { return; }
115
+
116
+ var body = item(key, section.labelKey).body;
117
+ parts.forEach(function(part) { body.appendChild(dom.addClass(part, 'ge-style-part')); });
118
+
119
+ if (properties.length) {
120
+ var note = body.appendChild(dom.element('small', { 'class': 'ge-style-note ge-style-all-sizes' }, ge.t('style.all_sizes')));
121
+ notes.push(note);
122
+
123
+ var grid = body.appendChild(dom.element('div', { 'class': 'ge-style-fields' }));
124
+ properties.forEach(function(property) {
125
+ var field = createStyleField(ge, node, property, changed);
126
+ fields.push(field);
127
+ grid.appendChild(field);
128
+ });
129
+ }
130
+
131
+ if (catalog) {
132
+ catalogs.push(catalog);
133
+ body.appendChild(catalog);
134
+ }
135
+ });
136
+
137
+ // Last, since it shows what the fields made above do not
138
+ if (customItem) {
139
+ custom = createCustom(ge, node, fields.map(function(field) {
140
+ return field.getAttribute('data-ge-style-property');
141
+ }), changed);
142
+ customItem.body.appendChild(custom.element);
143
+ accordion.appendChild(customItem.entry);
144
+ }
145
+
146
+ if (!accordion.children.length) { return null; }
147
+
148
+ /** Open this section and no other; null closes them all. */
149
+ function open(key) {
150
+ dom.all(accordion, '.accordion-item').forEach(function(entry) {
151
+ toggle(entry, entry.getAttribute('data-ge-style-section') === key);
152
+ });
153
+ }
154
+
155
+ open(context.state.open);
156
+
157
+ /** Fill everything from the node again: after a write, a change of classes or view. */
158
+ function render() {
159
+ fields.forEach(renderStyleField);
160
+ catalogs.forEach(function(catalog) { renderCatalog(catalog, node); });
161
+ if (custom) { custom.render(); }
162
+ notes.forEach(function(note) { dom.toggle(note, ge.view() !== 'all'); });
163
+ }
164
+
165
+ render();
166
+
167
+ return { element: accordion, render: render, open: open };
168
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The style plugin's catalog: Bootstrap's classes for what a section
3
+ * styles, as chips that put a class on the node or take it off.
4
+ *
5
+ * A chip never touches the node itself. It writes the panel's classes
6
+ * field and fires its change, as the user typing there would, so the
7
+ * editor keeps the node's own classes apart from its editing ones, and the
8
+ * Responsive section and the preview follow. Within an exclusive group,
9
+ * choosing one class takes the others off.
10
+ */
11
+ import * as dom from '../dom.js';
12
+ import { offeredOn } from './sections.js';
13
+
14
+ function classesOf(node) {
15
+ return (node.getAttribute('class') || '').split(/\s+/).filter(Boolean);
16
+ }
17
+
18
+ /** Put a class on or off through the node's classes field. */
19
+ function choose(ge, node, group, name) {
20
+ var details = ge.detailsOf(node);
21
+ var input = details ? dom.one(details, '.ge-classes') : null;
22
+ if (!input) { return; }
23
+
24
+ var on = classesOf(node).indexOf(name) !== -1;
25
+ var list = input.value.split(/\s+/).filter(Boolean).filter(function(each) {
26
+ if (each === name) { return false; }
27
+ return !(group.exclusive && !on && group.classes.indexOf(each) !== -1);
28
+ });
29
+
30
+ if (!on) { list.push(name); }
31
+
32
+ input.value = list.join(' ');
33
+ input.dispatchEvent(new Event('change', { bubbles: true }));
34
+ }
35
+
36
+ /**
37
+ * A section's catalog for a node, or null when none of its groups is
38
+ * offered on this kind of node.
39
+ */
40
+ export function createCatalog(ge, node, kind, groups) {
41
+ var offered = groups.filter(function(group) { return offeredOn(group, kind); });
42
+ if (!offered.length) { return null; }
43
+
44
+ var box = dom.element('div', { 'class': 'ge-style-catalog' });
45
+ box.appendChild(dom.element('span', { 'class': 'ge-style-label' }, ge.t('style.catalog')));
46
+
47
+ offered.forEach(function(group) {
48
+ var row = box.appendChild(dom.element('div', { 'class': 'ge-style-chips' }));
49
+
50
+ group.classes.forEach(function(name) {
51
+ var chip = row.appendChild(dom.element('button', {
52
+ type: 'button',
53
+ 'class': 'btn btn-sm btn-outline-secondary ge-style-chip',
54
+ 'data-ge-class': name,
55
+ 'aria-pressed': 'false',
56
+ }, name));
57
+
58
+ chip.addEventListener('click', function() { choose(ge, node, group, name); });
59
+ });
60
+ });
61
+
62
+ renderCatalog(box, node);
63
+
64
+ return box;
65
+ }
66
+
67
+ /** A chip is pressed while its class is on the node. */
68
+ export function renderCatalog(box, node) {
69
+ var classes = classesOf(node);
70
+
71
+ dom.all(box, '.ge-style-chip').forEach(function(chip) {
72
+ var on = classes.indexOf(chip.getAttribute('data-ge-class')) !== -1;
73
+ dom.toggleClass(chip, 'active', on);
74
+ chip.setAttribute('aria-pressed', on ? 'true' : 'false');
75
+ });
76
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The style plugin's free css: a textarea with every declaration of the
3
+ * node's own style that no field on show stands for - whatever the host
4
+ * wrote that the sections have no field for, and what a section the host
5
+ * turned off or narrowed would have shown.
6
+ *
7
+ * Compared longhand by longhand, which is how the browser holds a style: a
8
+ * margin written as a shorthand is its four sides, and a field for
9
+ * border-width stands for the four border-*-width. Editing the textarea
10
+ * writes those declarations again; the browser drops what it cannot
11
+ * parse, and the textarea shows what was kept.
12
+ */
13
+ import * as dom from '../dom.js';
14
+
15
+ /** The longhands a property sets, as the browser expands it. */
16
+ function longhands(property) {
17
+ var probe = document.createElement('div').style;
18
+ var list = [];
19
+
20
+ probe.setProperty(property, 'initial');
21
+ for (var i = 0; i < probe.length; i++) { list.push(probe[i]); }
22
+
23
+ return list.length ? list : [property];
24
+ }
25
+
26
+ function declaration(cssText) {
27
+ var style = document.createElement('div').style;
28
+ style.cssText = cssText;
29
+ return style;
30
+ }
31
+
32
+ /** `properties` are the ones the node's fields on show stand for. */
33
+ export function createCustom(ge, node, properties, changed) {
34
+ var covered = {};
35
+ properties.forEach(function(property) {
36
+ longhands(property).forEach(function(longhand) { covered[longhand] = true; });
37
+ });
38
+
39
+ var box = dom.element('div', { 'class': 'ge-style-custom' });
40
+ var textarea = box.appendChild(dom.element('textarea', {
41
+ 'class': 'form-control form-control-sm font-monospace ge-style-custom-css',
42
+ rows: '4',
43
+ spellcheck: 'false',
44
+ 'aria-label': ge.t('style.section_custom'),
45
+ }));
46
+
47
+ /** The declarations of the host's style no field stands for. */
48
+ function free() {
49
+ var style = declaration(ge.hostStyle(node));
50
+ var rest = document.createElement('div').style;
51
+
52
+ for (var i = 0; i < style.length; i++) {
53
+ var name = style[i];
54
+ if (!covered[name]) { rest.setProperty(name, style.getPropertyValue(name), style.getPropertyPriority(name)); }
55
+ }
56
+
57
+ return rest;
58
+ }
59
+
60
+ textarea.addEventListener('change', function() {
61
+ var before = free();
62
+ var wanted = declaration(textarea.value);
63
+ var i;
64
+
65
+ for (i = 0; i < before.length; i++) {
66
+ if (wanted.getPropertyValue(before[i]) === '') { ge.setHostStyle(node, before[i], ''); }
67
+ }
68
+ for (i = 0; i < wanted.length; i++) {
69
+ ge.setHostStyle(node, wanted[i], wanted.getPropertyValue(wanted[i]), wanted.getPropertyPriority(wanted[i]));
70
+ }
71
+
72
+ render();
73
+ changed();
74
+ });
75
+
76
+ function render() {
77
+ if (document.activeElement === textarea) { return; }
78
+ textarea.value = free().cssText.replace(/; /g, ';\n');
79
+ }
80
+
81
+ render();
82
+
83
+ return { element: box, render: render };
84
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Display, a part of the style plugin: how a node is displayed per
3
+ * breakpoint, hidden included, with Bootstrap's d-{breakpoint}-* classes,
4
+ * and the eye in the drawers that hides and shows it.
5
+ *
6
+ * Its families, and whatever it does on the canvas, as a function of the
7
+ * handle and its options: grideditor.style.js puts them in its sections.
8
+ */
9
+ import { GridEditor } from '../grideditor.js';
10
+ import * as dom from '../dom.js';
11
+
12
+ Object.assign(GridEditor.locales.en, {
13
+ 'utility.display': 'Display',
14
+ 'utility.visibility_hidden': 'Hidden',
15
+ 'tool.hide_in_view': 'Hide in this view',
16
+ 'tool.show_in_view': 'Show in this view',
17
+ 'badge.hidden_in': 'Hidden at {breakpoints}',
18
+ });
19
+
20
+ /** Bootstrap's display values, less its table ones. */
21
+ var VALUES = ['none', 'inline', 'inline-block', 'block', 'grid', 'inline-grid', 'flex', 'inline-flex'];
22
+
23
+ /** A node that carries any class of the family, at any breakpoint. */
24
+ var CLASS_PATTERN = new RegExp('(?:^|\\s)d-(?:(?:sm|md|lg|xl|xxl)-)?(?:' + VALUES.join('|') + ')(?:\\s|$)');
25
+ var NONE_PATTERN = /^d-(?:(?:sm|md|lg|xl|xxl)-)?none$/;
26
+
27
+ var NODES = '.row, .column, .ge-content, .ge-element, [data-ge-container]';
28
+
29
+ /** What the canvas shows a hidden node as, in a view where Bootstrap hides it. */
30
+ var SHOWN_ATTR = 'data-ge-display';
31
+
32
+ export function displayPart(ge, given) {
33
+
34
+ var options = Object.assign({ drawer: true }, given);
35
+
36
+ /**
37
+ * What a kind of node is offered: a row is a flex container or it is no
38
+ * grid, and a text is a block of text.
39
+ */
40
+ function choices(kind) {
41
+ if (kind === 'row') { return ['none', 'flex']; }
42
+ if (kind === 'text' || kind === 'plain') { return ['none', 'block']; }
43
+
44
+ return VALUES;
45
+ }
46
+
47
+ /** What "shown" is written as on this kind of node, when nothing below says. */
48
+ function shownByDefault(kind) {
49
+ return kind === 'row' ? 'flex' : 'block';
50
+ }
51
+
52
+ function applies(node, kind) {
53
+ return kind === 'row' || kind === 'column' || kind === 'element' || kind === 'text' || kind === 'plain' ||
54
+ dom.is(node, '[data-ge-container]');
55
+ }
56
+
57
+ function hiddenAt(node, view) {
58
+ return ge.getUtility(node, 'display', view) === 'none';
59
+ }
60
+
61
+ /** The breakpoints at which the node is hidden, smallest first. */
62
+ function hiddenTiers(node) {
63
+ return ge.breakpoints.filter(function(key) { return hiddenAt(node, key); });
64
+ }
65
+
66
+ /**
67
+ * Whether the node is hidden in the view being edited. In the all view
68
+ * that means hidden at every breakpoint, since "hidden at some" is what
69
+ * the badge is for.
70
+ */
71
+ function hiddenHere(node) {
72
+ return ge.view() === 'all'
73
+ ? hiddenTiers(node).length === ge.breakpoints.length
74
+ : hiddenAt(node, ge.view());
75
+ }
76
+
77
+ /** What the breakpoint below the view shows, which an inherit falls back to. */
78
+ function hiddenBelow(node) {
79
+ var index = ge.breakpoints.indexOf(ge.view());
80
+
81
+ return index > 0 && hiddenAt(node, ge.breakpoints[index - 1]);
82
+ }
83
+
84
+ /**
85
+ * How the nearest breakpoint below this one that shows the node shows
86
+ * it, or null when none below does.
87
+ */
88
+ function shownBelow(node, view) {
89
+ for (var i = ge.breakpoints.indexOf(view) - 1; i >= 0; i--) {
90
+ var value = ge.getUtility(node, 'display', ge.breakpoints[i]);
91
+
92
+ if (value === null) { return null; }
93
+ if (value !== 'none') { return value; }
94
+ }
95
+
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Hide or show the node in the view being edited, with as few classes as
101
+ * that takes. In a breakpoint view that is often none at all: showing a
102
+ * node the breakpoint below shows removes this breakpoint's d-*-none
103
+ * rather than writing another class on top of it. Showing one that the
104
+ * breakpoint below hides writes the display it had further down, so a
105
+ * flex column comes back flex. In the all view a node is hidden
106
+ * everywhere or shown everywhere, and shown is no class.
107
+ */
108
+ function toggle(node, kind) {
109
+ var hide = !hiddenHere(node);
110
+ var value;
111
+
112
+ if (ge.view() === 'all') {
113
+ value = hide ? 'none' : null;
114
+ } else if (hide) {
115
+ value = hiddenBelow(node) ? null : 'none';
116
+ } else {
117
+ value = hiddenBelow(node) ? (shownBelow(node, ge.view()) || shownByDefault(kind)) : null;
118
+ }
119
+
120
+ ge.setUtility(node, 'display', value, { source: 'tool' });
121
+ }
122
+
123
+ /**
124
+ * A node Bootstrap hides in this view stays on the canvas, shown as it
125
+ * would be if it were not hidden: with its classes' none taken off for
126
+ * the moment it takes to ask the browser.
127
+ */
128
+ function shownAs(node) {
129
+ var original = node.getAttribute('class');
130
+
131
+ node.setAttribute('class', original.split(/\s+/).filter(function(name) {
132
+ return !NONE_PATTERN.test(name);
133
+ }).join(' '));
134
+
135
+ var display = getComputedStyle(node).display;
136
+ node.setAttribute('class', original);
137
+
138
+ // The canvas has a rule for each of Bootstrap's values, and a block for anything else
139
+ return display !== 'none' && VALUES.indexOf(display) !== -1 ? display : 'block';
140
+ }
141
+
142
+ /**
143
+ * The canvas shows each node as the view sees it. A node Bootstrap would
144
+ * hide is kept on the canvas with the display it would have, and
145
+ * ge-hidden-in-view fades it; data-ge-hidden-in carries the badge text
146
+ * for the all view.
147
+ */
148
+ function mark(scope) {
149
+ dom.selfAndAll(scope, NODES).forEach(function(node) {
150
+ var kind = ge.kindOf(node);
151
+ var carries = applies(node, kind) && CLASS_PATTERN.test(node.getAttribute('class') || '');
152
+ var tiers = carries ? hiddenTiers(node) : [];
153
+ var here = carries && hiddenHere(node);
154
+ var partly = carries && ge.view() === 'all' && tiers.length > 0 && !here;
155
+
156
+ node.removeAttribute(SHOWN_ATTR);
157
+ if (carries && getComputedStyle(node).display === 'none') {
158
+ node.setAttribute(SHOWN_ATTR, shownAs(node));
159
+ }
160
+
161
+ dom.toggleClass(node, 'ge-hidden-in-view', here);
162
+
163
+ if (partly) {
164
+ node.setAttribute('data-ge-hidden-in', ge.t('badge.hidden_in', { breakpoints: tiers.join(', ') }));
165
+ } else {
166
+ node.removeAttribute('data-ge-hidden-in');
167
+ }
168
+
169
+ dom.children(node, '.ge-tools-drawer').forEach(function(drawer) {
170
+ dom.children(drawer, '.ge-visibility-tool').forEach(function(tool) {
171
+ tool.setAttribute('title', here ? ge.t('tool.show_in_view') : ge.t('tool.hide_in_view'));
172
+ dom.all(tool, 'i').forEach(function(icon) {
173
+ icon.setAttribute('class', here ? 'bi bi-eye-slash' : 'bi bi-eye');
174
+ });
175
+ });
176
+ });
177
+ });
178
+ }
179
+
180
+ function unmark() {
181
+ dom.all(ge.canvas, '.ge-hidden-in-view, [data-ge-hidden-in], [' + SHOWN_ATTR + ']').forEach(function(node) {
182
+ dom.removeClass(node, 'ge-hidden-in-view');
183
+ node.removeAttribute('data-ge-hidden-in');
184
+ node.removeAttribute(SHOWN_ATTR);
185
+ dom.dropEmptyClass(node);
186
+ });
187
+ }
188
+
189
+ return {
190
+ families: [{
191
+ name: 'display',
192
+ prefix: 'd',
193
+ values: VALUES,
194
+ appliesTo: ['row', 'column', 'text', 'element', 'container'],
195
+ labelKey: 'utility.display',
196
+
197
+ choices: function(node, kind) {
198
+ return choices(kind);
199
+ },
200
+
201
+ label: function(value) {
202
+ return value === 'none' ? ge.t('utility.visibility_hidden') : value;
203
+ },
204
+
205
+ /**
206
+ * What the view being edited displays the node as. Hidden is
207
+ * shown the way the breakpoints below show it, since the canvas
208
+ * keeps a hidden node, faded; with no class applying, it is what
209
+ * the node is without any.
210
+ */
211
+ preview: function(value, node) {
212
+ if (value === 'none') { value = shownBelow(node, ge.view()); }
213
+
214
+ return { display: value === null ? ge.bareStyle(node, 'display', 'display') : value };
215
+ },
216
+ }],
217
+
218
+ drawerTools: function(drawer, node, kind) {
219
+ if (!options.drawer || !applies(node, kind)) { return; }
220
+
221
+ ge.createTool(drawer, ge.t('tool.hide_in_view'), 'ge-visibility-tool', 'bi bi-eye', function() {
222
+ toggle(node, kind);
223
+ });
224
+ },
225
+
226
+ onRefresh: mark,
227
+ onDeinit: unmark,
228
+ };
229
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The drawer of a flex or grid node, a part of the style plugin.
3
+ *
4
+ * A drawer is its node's first child, in the flow: in a row, which wraps,
5
+ * it is a line of its own above the columns, but in anything else that
6
+ * lays its children out - a column made flex, a row that does not wrap, a
7
+ * grid - it would be one of the items, beside the content or in a cell of
8
+ * its own. So on such a node the drawer leaves the flow: it sits on top, as
9
+ * wide as the node, over a transparent top border as tall as it is, and
10
+ * the content is laid out below as the page would lay it out.
11
+ *
12
+ * The node's own style is the host's, so what is written on it is an
13
+ * attribute, and the border comes from a rule per height, here.
14
+ */
15
+ import * as dom from '../dom.js';
16
+
17
+ var NODES = '.row, .column, .ge-element, [data-ge-container]';
18
+ var OUT_CLASS = 'ge-drawer-out';
19
+ var OUT_ATTR = 'data-ge-drawer-out';
20
+
21
+ /** The space left between the drawer and the content, as the editor's frame leaves it. */
22
+ var GAP = 5;
23
+
24
+ var LAYS_OUT = /^(?:inline-)?(?:flex|grid)$/;
25
+
26
+ export function drawerflowPart(ge) {
27
+
28
+ var sheet = null;
29
+ var heights = {};
30
+ var observer = null;
31
+
32
+ /** The rule for a node whose drawer takes this much room. */
33
+ function rule(room) {
34
+ if (heights[room]) { return; }
35
+ heights[room] = true;
36
+
37
+ if (!sheet) {
38
+ sheet = document.head.appendChild(dom.element('style', { 'data-ge-drawer-flow': '' }));
39
+ }
40
+
41
+ var node = '.ge-canvas.ge-editing [' + OUT_ATTR + '="' + room + '"]';
42
+ sheet.appendChild(document.createTextNode(
43
+ node + ' { border-top-width: ' + room + 'px !important; }\n' +
44
+ node + ' > .ge-tools-drawer { top: -' + room + 'px; }\n'
45
+ ));
46
+ }
47
+
48
+ /** Whether the node lays its drawer out as an item of its own. */
49
+ function laysOut(node) {
50
+ var style = getComputedStyle(node);
51
+
52
+ if (!LAYS_OUT.test(style.display)) { return false; }
53
+
54
+ // A row as the grid has it: its drawer is a line of its own already
55
+ return !(dom.hasClass(node, 'row') && style.flexDirection === 'row' && style.flexWrap === 'wrap');
56
+ }
57
+
58
+ function measure(node, drawer) {
59
+ var room = Math.ceil(drawer.offsetHeight) + GAP;
60
+
61
+ rule(room);
62
+ node.setAttribute(OUT_ATTR, String(room));
63
+ }
64
+
65
+ /** A drawer that grows - its tools wrap, its settings unfold - takes more room. */
66
+ function watch(drawer) {
67
+ if (typeof ResizeObserver === 'undefined') { return; }
68
+
69
+ if (!observer) {
70
+ observer = new ResizeObserver(function(entries) {
71
+ entries.forEach(function(entry) {
72
+ var node = entry.target.parentNode;
73
+ if (node && dom.hasClass(node, OUT_CLASS)) { measure(node, entry.target); }
74
+ });
75
+ });
76
+ }
77
+ observer.observe(drawer);
78
+ }
79
+
80
+ function mark(scope) {
81
+ dom.selfAndAll(scope, NODES).forEach(function(node) {
82
+ var drawer = dom.child(node, '.ge-tools-drawer');
83
+ if (!drawer) { return; }
84
+
85
+ if (laysOut(node)) {
86
+ dom.addClass(node, OUT_CLASS);
87
+ measure(node, drawer);
88
+ watch(drawer);
89
+ } else if (dom.hasClass(node, OUT_CLASS)) {
90
+ dom.removeClass(node, OUT_CLASS);
91
+ node.removeAttribute(OUT_ATTR);
92
+ dom.dropEmptyClass(node);
93
+ if (observer) { observer.unobserve(drawer); }
94
+ }
95
+ });
96
+ }
97
+
98
+ function unmark() {
99
+ if (observer) { observer.disconnect(); }
100
+ observer = null;
101
+
102
+ dom.all(ge.canvas, '.' + OUT_CLASS).forEach(function(node) {
103
+ dom.removeClass(node, OUT_CLASS);
104
+ node.removeAttribute(OUT_ATTR);
105
+ dom.dropEmptyClass(node);
106
+ });
107
+
108
+ if (sheet) { sheet.remove(); }
109
+ sheet = null;
110
+ heights = {};
111
+ }
112
+
113
+ return {
114
+ families: [],
115
+ onRefresh: mark,
116
+ onDeinit: unmark,
117
+ };
118
+ }