@themarioga/grid-editor 8.0.0-beta.2 → 8.0.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +16 -9
  3. package/UPGRADING.md +21 -11
  4. package/dist/grideditor.bundle.min.js +3 -3
  5. package/dist/grideditor.css +77 -35
  6. package/dist/grideditor.d.ts +29 -8
  7. package/dist/grideditor.esm.js +7 -1
  8. package/dist/grideditor.js +7 -1
  9. package/dist/grideditor.min.css +1 -1
  10. package/dist/grideditor.min.css.map +1 -1
  11. package/dist/grideditor.min.js +2 -2
  12. package/dist/grideditor.min.js.map +3 -3
  13. package/dist/locales/grideditor.es.esm.js +86 -68
  14. package/dist/locales/grideditor.es.js +86 -68
  15. package/dist/locales/grideditor.es.min.js +1 -1
  16. package/dist/locales/grideditor.es.min.js.map +2 -2
  17. package/dist/plugins/grideditor.alignment.min.js.map +1 -1
  18. package/dist/plugins/{grideditor.style.esm.js → grideditor.inline-style.esm.js} +184 -184
  19. package/dist/plugins/{grideditor.style.js → grideditor.inline-style.js} +182 -182
  20. package/dist/plugins/grideditor.inline-style.min.js +4 -0
  21. package/dist/plugins/grideditor.inline-style.min.js.map +7 -0
  22. package/dist/plugins/grideditor.tabs.esm.js +274 -6
  23. package/dist/plugins/grideditor.tabs.js +274 -6
  24. package/dist/plugins/grideditor.tabs.min.js +1 -1
  25. package/dist/plugins/grideditor.tabs.min.js.map +3 -3
  26. package/docs/locale-keys.md +97 -79
  27. package/docs/plugins.md +5 -5
  28. package/example/containers.html +64 -2
  29. package/example/index.html +6 -6
  30. package/example/{style.html → inline-style.html} +5 -5
  31. package/example/utilities.html +3 -3
  32. package/package.json +1 -1
  33. package/src/js/grideditor.js +8 -0
  34. package/src/js/{style → inline-style}/accordion.js +9 -9
  35. package/src/js/{style → inline-style}/catalog.js +6 -6
  36. package/src/js/{style → inline-style}/custom.js +4 -4
  37. package/src/js/{style → inline-style}/display.js +2 -2
  38. package/src/js/{style → inline-style}/drawerflow.js +1 -1
  39. package/src/js/{style → inline-style}/fields.js +25 -25
  40. package/src/js/{style → inline-style}/flex.js +2 -2
  41. package/src/js/{style → inline-style}/float.js +2 -2
  42. package/src/js/{style → inline-style}/options.js +5 -5
  43. package/src/js/{style → inline-style}/sections.js +50 -50
  44. package/src/js/{style → inline-style}/spacing.js +2 -2
  45. package/src/js/{style → inline-style}/sticky.js +2 -2
  46. package/src/js/{style → inline-style}/textalign.js +2 -2
  47. package/src/js/locales/grideditor.es.js +86 -68
  48. package/src/js/plugins/grideditor.alignment.js +1 -1
  49. package/src/js/plugins/grideditor.inline-style.js +185 -0
  50. package/src/js/plugins/grideditor.tabs.js +311 -5
  51. package/src/less/grideditor.less +71 -25
  52. package/types/grideditor.d.ts +29 -8
  53. package/dist/plugins/grideditor.style.min.js +0 -4
  54. package/dist/plugins/grideditor.style.min.js.map +0 -7
  55. package/src/js/plugins/grideditor.style.js +0 -185
@@ -7,6 +7,12 @@
7
7
  *
8
8
  * <script src="dist/grideditor.min.js"></script>
9
9
  * <script src="dist/plugins/grideditor.tabs.min.js"></script>
10
+ *
11
+ * A container's variant - its style, width, alignment and whether it is laid
12
+ * out vertically, from a breakpoint up - is Bootstrap's classes on its strip
13
+ * and on itself, and nothing else: it is read from the markup, chosen in the
14
+ * container's settings panel, or given to createContainer and the tabs
15
+ * setting for the ones made new.
10
16
  */
11
17
  import { GridEditor } from '../grideditor.js';
12
18
  import * as dom from '../dom.js';
@@ -16,14 +22,287 @@ Object.assign(GridEditor.locales.en, {
16
22
  'container.add_tab': 'Add tab',
17
23
  'container.tab_label': 'Tab {number}',
18
24
  'confirm.delete_tab': 'Delete this tab and everything in it?',
25
+ 'container.tabs_section': 'Tabs',
26
+ 'container.tabs_style': 'Style',
27
+ 'container.tabs_style_tabs': 'Tabs',
28
+ 'container.tabs_style_pills': 'Pills',
29
+ 'container.tabs_style_underline': 'Underline',
30
+ 'container.tabs_width': 'Width',
31
+ 'container.tabs_width_natural': 'Natural',
32
+ 'container.tabs_width_fill': 'Fill',
33
+ 'container.tabs_width_justified': 'Justified',
34
+ 'container.tabs_align': 'Alignment',
35
+ 'container.tabs_align_start': 'Start',
36
+ 'container.tabs_align_center': 'Center',
37
+ 'container.tabs_align_end': 'End',
38
+ 'container.tabs_layout': 'Layout',
39
+ 'container.tabs_layout_horizontal': 'Horizontal',
40
+ 'container.tabs_layout_vertical': 'Vertical',
41
+ 'container.tabs_layout_vertical_from': 'Vertical from {breakpoint}',
19
42
  });
20
43
 
44
+ /** Each variant's values, the first its default, and the class each puts on the strip. */
45
+ var STYLES = { tabs: 'nav-tabs', pills: 'nav-pills', underline: 'nav-underline' };
46
+ var WIDTHS = { natural: null, fill: 'nav-fill', justified: 'nav-justified' };
47
+ var ALIGNS = { start: null, center: 'justify-content-center', end: 'justify-content-end' };
48
+ var DEFAULTS = { variant: 'tabs', width: 'natural', align: 'start', vertical: false };
49
+
50
+ /** Each variant's select in the panel: its label, and its choices'. */
51
+ var FIELDS = {
52
+ variant: { labelKey: 'container.tabs_style', choices: [
53
+ { value: 'tabs', labelKey: 'container.tabs_style_tabs' },
54
+ { value: 'pills', labelKey: 'container.tabs_style_pills' },
55
+ { value: 'underline', labelKey: 'container.tabs_style_underline' },
56
+ ] },
57
+ width: { labelKey: 'container.tabs_width', choices: [
58
+ { value: 'natural', labelKey: 'container.tabs_width_natural' },
59
+ { value: 'fill', labelKey: 'container.tabs_width_fill' },
60
+ { value: 'justified', labelKey: 'container.tabs_width_justified' },
61
+ ] },
62
+ align: { labelKey: 'container.tabs_align', choices: [
63
+ { value: 'start', labelKey: 'container.tabs_align_start' },
64
+ { value: 'center', labelKey: 'container.tabs_align_center' },
65
+ { value: 'end', labelKey: 'container.tabs_align_end' },
66
+ ] },
67
+ vertical: { labelKey: 'container.tabs_layout' },
68
+ };
69
+
70
+ var fieldCounter = 0;
71
+
72
+ /** Where the layout marks what the view being edited lays the tabs out as. */
73
+ var LAYOUT_ATTR = 'data-ge-tabs-layout';
74
+
75
+ function classValues(map) {
76
+ return Object.keys(map).map(function(key) { return map[key]; }).filter(Boolean);
77
+ }
78
+
21
79
  GridEditor.containers.tabs = function(ge) {
22
80
 
81
+ var sections = []; // { container, render } for every panel section built
82
+
83
+ /** The strip: the container's first .nav, whatever its style. */
84
+ function stripOf(container) {
85
+ return dom.child(container, '.nav');
86
+ }
87
+
88
+ /** The breakpoints a layout can go vertical from; xs is always. */
89
+ function fromBreakpoints() {
90
+ return ge.breakpoints.slice(1);
91
+ }
92
+
93
+ /** The infix of a vertical layout's classes: none for always. */
94
+ function infix(vertical) {
95
+ return vertical === true ? '' : '-' + vertical;
96
+ }
97
+
98
+ function verticalClasses(vertical) {
99
+ var at = infix(vertical);
100
+
101
+ return {
102
+ container: ['d' + at + '-flex', 'align-items' + at + '-start'],
103
+ strip: ['flex' + at + '-column', 'me' + at + '-3'],
104
+ };
105
+ }
106
+
107
+ /** Every vertical value: always, and from each breakpoint. */
108
+ function verticals() {
109
+ return [true].concat(fromBreakpoints());
110
+ }
111
+
112
+ /**
113
+ * What a container's classes say it is. Vertical is read only when the
114
+ * container's flex and the strip's column are of the same breakpoint;
115
+ * anything else is horizontal, and is left as it is.
116
+ */
117
+ function read(container) {
118
+ var strip = stripOf(container);
119
+ var has = function(node, name) { return !!node && dom.hasClass(node, name); };
120
+ var found = function(map, fallback) {
121
+ return Object.keys(map).filter(function(key) { return map[key] && has(strip, map[key]); })[0] || fallback;
122
+ };
123
+
124
+ var vertical = verticals().filter(function(each) {
125
+ var classes = verticalClasses(each);
126
+ return has(container, classes.container[0]) && has(strip, classes.strip[0]);
127
+ })[0];
128
+
129
+ return {
130
+ variant: found(STYLES, 'tabs'),
131
+ width: found(WIDTHS, 'natural'),
132
+ align: found(ALIGNS, 'start'),
133
+ vertical: vertical === undefined ? false : vertical,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Write one variant, and take off what it makes meaningless: a vertical
139
+ * layout has no width or alignment, and a full width no alignment.
140
+ * Choosing a layout clears every layout class, at any breakpoint, the
141
+ * user's own included.
142
+ */
143
+ function write(container, key, value) {
144
+ var strip = stripOf(container);
145
+ if (!strip) { return; }
146
+
147
+ if (key === 'variant') {
148
+ dom.removeClass(strip, classValues(STYLES).join(' '));
149
+ dom.addClass(strip, STYLES[value]);
150
+ }
151
+
152
+ if (key === 'width' || key === 'vertical') {
153
+ dom.removeClass(strip, classValues(WIDTHS).join(' '));
154
+ if (key === 'width' && WIDTHS[value]) { dom.addClass(strip, WIDTHS[value]); }
155
+ }
156
+
157
+ if (key === 'align' || key === 'vertical' || (key === 'width' && value !== 'natural')) {
158
+ dom.removeClass(strip, classValues(ALIGNS).join(' '));
159
+ if (key === 'align' && ALIGNS[value]) { dom.addClass(strip, ALIGNS[value]); }
160
+ }
161
+
162
+ if (key === 'vertical') {
163
+ verticals().forEach(function(each) {
164
+ var classes = verticalClasses(each);
165
+ dom.removeClass(container, classes.container.join(' '));
166
+ dom.removeClass(strip, classes.strip.join(' '));
167
+ });
168
+ strip.removeAttribute('aria-orientation');
169
+
170
+ if (value !== false) {
171
+ var layout = verticalClasses(value);
172
+ dom.addClass(container, layout.container.join(' '));
173
+ dom.addClass(strip, layout.strip.join(' '));
174
+ strip.setAttribute('aria-orientation', 'vertical');
175
+ }
176
+ }
177
+
178
+ dom.dropEmptyClass(container);
179
+ }
180
+
181
+ /** The values a variant takes, as the warning lists them. */
182
+ function valuesOf(key) {
183
+ if (key === 'variant') { return Object.keys(STYLES); }
184
+ if (key === 'width') { return Object.keys(WIDTHS); }
185
+ if (key === 'align') { return Object.keys(ALIGNS); }
186
+
187
+ return [false].concat(verticals());
188
+ }
189
+
190
+ /**
191
+ * A variant for a new container: the option, or the tabs setting, or the
192
+ * default. A value it does not take is warned about and the default used.
193
+ */
194
+ function chosen(options, key) {
195
+ var setting = ge.settings.tabs || {};
196
+ var value = options[key] !== undefined ? options[key] : setting[key];
197
+
198
+ if (value === undefined) { return DEFAULTS[key]; }
199
+ if (valuesOf(key).indexOf(value) !== -1) { return value; }
200
+
201
+ ge.warn('tabs: ' + JSON.stringify(value) + ' is not a ' + key + ', which takes ' +
202
+ JSON.stringify(valuesOf(key)) + ': ' + JSON.stringify(DEFAULTS[key]) + ' is used');
203
+ return DEFAULTS[key];
204
+ }
205
+
206
+ /**
207
+ * What the view being edited lays a container out as. A breakpoint view
208
+ * narrows the canvas, not the window, and Bootstrap's classes answer to
209
+ * the window: so the view says it, from the breakpoint the container
210
+ * goes vertical at. The all view shows what the window gives.
211
+ */
212
+ function layoutHere(container) {
213
+ var vertical = read(container).vertical;
214
+ if (vertical === false) { return null; }
215
+
216
+ if (ge.view() === 'all') {
217
+ container.removeAttribute(LAYOUT_ATTR);
218
+ return /flex$/.test(getComputedStyle(container).display) ? 'vertical' : 'horizontal';
219
+ }
220
+ if (vertical === true) { return 'vertical'; }
221
+
222
+ return ge.breakpoints.indexOf(ge.view()) >= ge.breakpoints.indexOf(vertical) ? 'vertical' : 'horizontal';
223
+ }
224
+
225
+ function markLayout(container) {
226
+ var layout = layoutHere(container);
227
+
228
+ if (layout) {
229
+ container.setAttribute(LAYOUT_ATTR, layout);
230
+ } else {
231
+ container.removeAttribute(LAYOUT_ATTR);
232
+ }
233
+ }
234
+
235
+ function select(labelKey, options, onChange) {
236
+ var box = dom.element('div', { 'class': 'ge-tabs-variant' });
237
+ var id = 'ge-tabs-variant-' + (++fieldCounter);
238
+
239
+ box.appendChild(dom.element('label', { 'class': 'form-label', 'for': id }, ge.t(labelKey)));
240
+ var field = box.appendChild(dom.element('select', { 'class': 'form-select form-select-sm', id: id }));
241
+
242
+ options.forEach(function(option) {
243
+ field.appendChild(dom.element('option', { value: option.value }, option.label));
244
+ });
245
+ field.addEventListener('change', function() { onChange(field.value); });
246
+
247
+ return { box: box, field: field };
248
+ }
249
+
250
+ /** The Tabs section of a tabs container's settings panel: a select per variant. */
251
+ function createSection(container) {
252
+ var body = dom.element('div', { 'class': 'ge-tabs-variants' });
253
+ var fields = {};
254
+
255
+ function change(key, value) {
256
+ write(container, key, value);
257
+ markLayout(container);
258
+ render();
259
+ }
260
+
261
+ function options(key) {
262
+ return FIELDS[key].choices.map(function(choice) {
263
+ return { value: choice.value, label: ge.t(choice.labelKey) };
264
+ });
265
+ }
266
+
267
+ ['variant', 'width', 'align'].forEach(function(key) {
268
+ fields[key] = select(FIELDS[key].labelKey, options(key), function(value) { change(key, value); });
269
+ });
270
+ fields.vertical = select(FIELDS.vertical.labelKey, [
271
+ { value: 'false', label: ge.t('container.tabs_layout_horizontal') },
272
+ { value: 'true', label: ge.t('container.tabs_layout_vertical') },
273
+ ].concat(fromBreakpoints().map(function(key) {
274
+ return { value: key, label: ge.t('container.tabs_layout_vertical_from', { breakpoint: key }) };
275
+ })), function(value) {
276
+ change('vertical', value === 'true' ? true : value === 'false' ? false : value);
277
+ });
278
+
279
+ ['variant', 'width', 'align', 'vertical'].forEach(function(key) {
280
+ fields[key].box.setAttribute('data-ge-tabs-variant', key);
281
+ body.appendChild(fields[key].box);
282
+ });
283
+
284
+ /** Fill the selects from the classes, and turn off what does not apply. */
285
+ function render() {
286
+ var values = read(container);
287
+
288
+ fields.variant.field.value = values.variant;
289
+ fields.width.field.value = values.width;
290
+ fields.align.field.value = values.align;
291
+ fields.vertical.field.value = String(values.vertical);
292
+ fields.width.field.disabled = values.vertical !== false;
293
+ fields.align.field.disabled = values.vertical !== false || values.width !== 'natural';
294
+ }
295
+
296
+ render();
297
+ sections.push({ container: container, element: body, render: render });
298
+
299
+ return body;
300
+ }
301
+
23
302
  function addTabTo(container, options) {
24
303
  options = options || {};
25
304
 
26
- var strip = dom.one(container, ':scope > .nav-tabs');
305
+ var strip = stripOf(container);
27
306
  var content = dom.one(container, ':scope > .tab-content');
28
307
  var id = ge.containerId('tab');
29
308
  var number = dom.children(strip, '.nav-item').length + 1;
@@ -61,7 +340,7 @@ GridEditor.containers.tabs = function(ge) {
61
340
  });
62
341
  dom.addClass(pane, 'show active');
63
342
 
64
- dom.all(container, ':scope > .nav-tabs .nav-link').forEach(function(button) {
343
+ dom.all(stripOf(container) || container, '.nav-link').forEach(function(button) {
65
344
  var active = button.getAttribute('data-bs-target') === '#' + id;
66
345
 
67
346
  dom.toggleClass(button, 'active', active);
@@ -82,7 +361,7 @@ GridEditor.containers.tabs = function(ge) {
82
361
  // A tab strip sorts its own tabs, and the panes follow them. No
83
362
  // group: a tab belongs to the strip it was made in.
84
363
  onSortable: function(sortable) {
85
- sortable(dom.all(ge.canvas, '.ge-container-tabs > .nav-tabs'), {
364
+ sortable(dom.all(ge.canvas, '.ge-container-tabs').map(stripOf).filter(Boolean), {
86
365
  draggable: '.ge-tab',
87
366
  });
88
367
  },
@@ -97,6 +376,11 @@ GridEditor.containers.tabs = function(ge) {
97
376
  container.appendChild(dom.element('ul', { 'class': 'nav nav-tabs', role: 'tablist' }));
98
377
  container.appendChild(dom.element('div', { 'class': 'tab-content' }));
99
378
 
379
+ // The layout last: it takes off what a vertical strip has no use for
380
+ ['variant', 'width', 'align', 'vertical'].forEach(function(key) {
381
+ write(container, key, chosen(options, key));
382
+ });
383
+
100
384
  for (var i = 0; i < count; i++) {
101
385
  addTabTo(container, { label: labels[i] });
102
386
  }
@@ -111,7 +395,7 @@ GridEditor.containers.tabs = function(ge) {
111
395
  dom.addClass(pane, 'ge-tab-pane');
112
396
  });
113
397
 
114
- dom.all(container, ':scope > .nav-tabs > .nav-item').forEach(function(tab) {
398
+ dom.children(stripOf(container) || container, '.nav-item').forEach(function(tab) {
115
399
  dom.addClass(tab, 'ge-tab');
116
400
 
117
401
  var link = dom.one(tab, '.nav-link');
@@ -137,17 +421,39 @@ GridEditor.containers.tabs = function(ge) {
137
421
 
138
422
  unmark: function(container) {
139
423
  ge.resumeToggles(container);
424
+ container.removeAttribute(LAYOUT_ATTR);
140
425
  dom.all(container, '.ge-tab-pane').forEach(function(pane) {
141
426
  dom.removeClass(pane, 'ge-tab-pane');
142
427
  });
143
428
  ge.unwrapLabels(container);
144
429
  },
145
430
 
431
+ panelSection: function(node, kind) {
432
+ if (kind !== 'tabs' || !stripOf(node)) { return null; }
433
+
434
+ return { labelKey: 'container.tabs_section', body: createSection(node) };
435
+ },
436
+
437
+ // After a view change or a change of classes: the layout marks and the
438
+ // sections follow
439
+ onRefresh: function(scope) {
440
+ dom.selfAndAll(scope, '[data-ge-container="tabs"]').forEach(markLayout);
441
+
442
+ sections = sections.filter(function(entry) { return entry.element.isConnected; });
443
+ sections.forEach(function(entry) {
444
+ if (entry.container === scope || scope.contains(entry.container)) { entry.render(); }
445
+ });
446
+ },
447
+
448
+ onDeinit: function() {
449
+ sections = [];
450
+ },
451
+
146
452
  /** Panes read in tab order, whatever order they were dropped in. */
147
453
  afterPaneMove: function(container) {
148
454
  var content = dom.one(container, ':scope > .tab-content');
149
455
 
150
- dom.all(container, ':scope > .nav-tabs > .nav-item').forEach(function(tab) {
456
+ dom.children(stripOf(container) || container, '.nav-item').forEach(function(tab) {
151
457
  var pane = paneOf(container, tab);
152
458
  if (pane) { content.appendChild(pane); }
153
459
  });
@@ -248,6 +248,19 @@
248
248
  flex-direction: column;
249
249
  gap: 8px;
250
250
  }
251
+ /* Each section's heading: the general fields, and what plugins add */
252
+ .ge-section-title {
253
+ margin: 0 0 6px;
254
+ color: var(--bs-body-color, #333);
255
+ font-size: 0.8125rem;
256
+ font-weight: 600;
257
+ }
258
+ /* Every section after the first, set off from the one above */
259
+ .ge-panel-section,
260
+ .ge-utilities {
261
+ padding-top: 10px;
262
+ border-top: 1px solid var(--bs-border-color-translucent, rgba(0,0,0,0.1));
263
+ }
251
264
  .ge-field {
252
265
  display: flex;
253
266
  flex-direction: column;
@@ -270,7 +283,7 @@
270
283
  > .ge-utilities-toggle {
271
284
  display: block;
272
285
  color: var(--bs-body-color, #333);
273
- font-weight: 500;
286
+ font-weight: 600;
274
287
  cursor: pointer;
275
288
  text-decoration: none;
276
289
 
@@ -342,9 +355,9 @@
342
355
  margin-top: 10px;
343
356
  }
344
357
 
345
- /* The style plugin's accordion: in a panel, or in the dialog, which is
358
+ /* The inline-style plugin's accordion: in a panel, or in the dialog, which is
346
359
  outside any panel, so it carries the look of the fields it shares. */
347
- .ge-style {
360
+ .ge-inline-style {
348
361
  font-size: 0.8125rem;
349
362
 
350
363
  .accordion-button {
@@ -359,26 +372,26 @@
359
372
  padding: 8px 10px;
360
373
  }
361
374
 
362
- .ge-style-label,
375
+ .ge-inline-style-label,
363
376
  .ge-utility-label,
364
- .ge-style-shadow-label {
377
+ .ge-inline-style-shadow-label {
365
378
  color: var(--bs-secondary-color, #6c757d);
366
379
  font-size: 0.75rem;
367
380
  }
368
- .ge-style-note,
381
+ .ge-inline-style-note,
369
382
  .ge-utility-note,
370
383
  .ge-spacing-gutter {
371
384
  color: var(--bs-secondary-color, #666);
372
385
  font-size: 0.6875rem;
373
386
  }
374
387
 
375
- .ge-style-fields,
388
+ .ge-inline-style-fields,
376
389
  .ge-spacing {
377
390
  display: grid;
378
391
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
379
392
  gap: 8px 10px;
380
393
  }
381
- .ge-style-field,
394
+ .ge-inline-style-field,
382
395
  .ge-utility,
383
396
  .ge-spacing-group {
384
397
  display: flex;
@@ -393,47 +406,47 @@
393
406
  grid-column: 1 / -1;
394
407
  }
395
408
 
396
- .ge-style-color .form-control-color {
409
+ .ge-inline-style-color .form-control-color {
397
410
  flex: 0 0 2.25rem;
398
411
  padding: 2px;
399
412
  }
400
413
 
401
- .ge-style-shadow,
402
- .ge-style-field:has(.ge-style-shadow) {
414
+ .ge-inline-style-shadow,
415
+ .ge-inline-style-field:has(.ge-inline-style-shadow) {
403
416
  grid-column: 1 / -1;
404
417
  }
405
- .ge-style-shadow-builder {
418
+ .ge-inline-style-shadow-builder {
406
419
  display: grid;
407
420
  grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
408
421
  gap: 4px 8px;
409
422
  align-items: end;
410
423
  }
411
- .ge-style-shadow-part {
424
+ .ge-inline-style-shadow-part {
412
425
  display: flex;
413
426
  flex-direction: column;
414
427
  gap: 2px;
415
428
  margin: 0;
416
429
  }
417
- .ge-style-shadow-color {
430
+ .ge-inline-style-shadow-color {
418
431
  grid-column: span 2;
419
432
  }
420
- .ge-style-shadow-mode {
433
+ .ge-inline-style-shadow-mode {
421
434
  display: inline-block;
422
435
  margin-top: 2px;
423
436
  font-size: 0.6875rem;
424
437
  }
425
438
 
426
- .ge-style-catalog {
439
+ .ge-inline-style-catalog {
427
440
  display: flex;
428
441
  flex-direction: column;
429
442
  gap: 4px;
430
443
  }
431
- .ge-style-chips {
444
+ .ge-inline-style-chips {
432
445
  display: flex;
433
446
  flex-wrap: wrap;
434
447
  gap: 3px;
435
448
  }
436
- .ge-style-chip {
449
+ .ge-inline-style-chip {
437
450
  --bs-btn-padding-y: 0;
438
451
  --bs-btn-padding-x: 6px;
439
452
  --bs-btn-font-size: 0.6875rem;
@@ -523,7 +536,6 @@
523
536
  -moz-user-select: none;
524
537
  -ms-user-select: none;
525
538
  user-select: none;
526
- background: blue;
527
539
  margin: 0 -@editGutter @editGutter;
528
540
  display: block;
529
541
  width: calc(~"100% +" 2 * @editGutter);
@@ -547,6 +559,10 @@
547
559
  text-align: left;
548
560
  cursor: auto;
549
561
 
562
+ /* The one line a drawer has room for needs no heading */
563
+ > .ge-section-title {
564
+ display: none;
565
+ }
550
566
  .ge-details-general {
551
567
  flex-direction: row;
552
568
  align-items: flex-end;
@@ -568,6 +584,12 @@
568
584
  margin-right: 0;
569
585
 
570
586
  > .ge-tools-drawer {
587
+ /* Bootstrap pads every child of a row by half its gutter, and
588
+ caps it at the row's width: the drawer is not a column */
589
+ padding-left: 0;
590
+ padding-right: 0;
591
+ max-width: none;
592
+
571
593
  > a {
572
594
  color: #666;
573
595
  }
@@ -714,6 +736,34 @@
714
736
  }
715
737
  }
716
738
 
739
+ /* A tabs container laid out vertically from a breakpoint: Bootstrap's
740
+ classes answer to the window, and a breakpoint view narrows the canvas
741
+ instead, so the tabs plugin says which layout the view has and that is
742
+ what shows. The selectors outweigh Bootstrap's !important utilities.
743
+ Vertical, the container wraps, so its drawer is a line of its own
744
+ above the strip and the panes */
745
+ [data-ge-tabs-layout="vertical"] {
746
+ display: flex !important;
747
+ flex-wrap: wrap;
748
+ align-items: flex-start !important;
749
+
750
+ > .ge-container-drawer {
751
+ flex-shrink: 0;
752
+ }
753
+ > .nav {
754
+ flex-direction: column !important;
755
+ margin-right: 1rem !important;
756
+ }
757
+ }
758
+ [data-ge-tabs-layout="horizontal"] {
759
+ display: block !important;
760
+
761
+ > .nav {
762
+ flex-direction: row !important;
763
+ margin-right: 0 !important;
764
+ }
765
+ }
766
+
717
767
  /* The label is edited in place, so it needs to look like it can be */
718
768
  .ge-pane-label {
719
769
  &:hover {
@@ -1115,7 +1165,7 @@
1115
1165
  .ge-layout-modes();
1116
1166
 
1117
1167
  /*
1118
- * The style plugin's display. A node Bootstrap would hide is kept on the
1168
+ * The inline-style plugin's display. A node Bootstrap would hide is kept on the
1119
1169
  * canvas - there would be nothing left to click to show it again - and shown
1120
1170
  * as it would be if it were not hidden, which data-ge-display says. The
1121
1171
  * selectors outweigh Bootstrap's d-* classes, which are !important too.
@@ -1158,7 +1208,7 @@
1158
1208
  }
1159
1209
 
1160
1210
  /*
1161
- * The style plugin's drawer on a flex or grid node: out of the flow, on top
1211
+ * The inline-style plugin's drawer on a flex or grid node: out of the flow, on top
1162
1212
  * and as wide as the node, over a transparent top border as tall as it is.
1163
1213
  * The border's width comes with the node's data-ge-drawer-out, from a rule
1164
1214
  * per height the plugin writes. The selectors outweigh Bootstrap's border
@@ -1208,10 +1258,6 @@
1208
1258
  > .ge-tools-drawer {
1209
1259
  margin-top: 0;
1210
1260
  margin-bottom: calc(@editGutter - var(--bs-gutter-y));
1211
- /* Bootstrap pads every child of a row by half its gutter, the drawer
1212
- too; it keeps the padding a row with no gutter class gives it */
1213
- padding-left: .75rem;
1214
- padding-right: .75rem;
1215
1261
  }
1216
1262
 
1217
1263
  > .column {
@@ -53,16 +53,34 @@ export interface PresetClass {
53
53
  }
54
54
 
55
55
  /** A content filter: run on init and deinit with the canvas. A string names a function on window. */
56
- /** A section of the style plugin's accordion. */
57
- export type StyleSectionKey = 'size' | 'spacing' | 'border' | 'background' | 'text' | 'typography' | 'display' | 'flex' | 'position' | 'custom';
56
+ /** A section of the inline-style plugin's accordion. */
57
+ export type InlineStyleSectionKey = 'size' | 'spacing' | 'border' | 'background' | 'text' | 'typography' | 'display' | 'flex' | 'position' | 'custom';
58
58
 
59
59
  /** A section on or off, or on with some of its properties and without its catalog. */
60
- export type StyleSection = boolean | { properties?: string[]; catalog?: boolean };
60
+ export type InlineStyleSection = boolean | { properties?: string[]; catalog?: boolean };
61
61
 
62
- /** The style plugin's settings. */
63
- export interface StyleOptions {
62
+ /**
63
+ * A tabs container's variant: its strip's style, width and alignment, and
64
+ * whether it is laid out vertically - always, or from a breakpoint up.
65
+ */
66
+ export interface TabsOptions {
67
+ variant?: 'tabs' | 'pills' | 'underline';
68
+ width?: 'natural' | 'fill' | 'justified';
69
+ /** Only with the natural width, and horizontal. */
70
+ align?: 'start' | 'center' | 'end';
71
+ vertical?: boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
72
+ }
73
+
74
+ /** createContainer('tabs', …): how many tabs, their labels, and the variant. */
75
+ export interface CreateTabsOptions extends Placement, TabsOptions {
76
+ tabs?: number;
77
+ labels?: string[];
78
+ }
79
+
80
+ /** The inline-style plugin's settings. */
81
+ export interface InlineStyleOptions {
64
82
  /** Every section is on unless it is turned off here. */
65
- sections?: Partial<Record<StyleSectionKey, StyleSection>>;
83
+ sections?: Partial<Record<InlineStyleSectionKey, InlineStyleSection>>;
66
84
  /** The spacing section: the values offered, and what 0 to 5 come to - for the gaps too. */
67
85
  spacing?: { values?: string[]; scale?: string[] };
68
86
  /** The eye that hides and shows a node, the display family's: false leaves it out of the drawers. */
@@ -95,8 +113,10 @@ export interface GridEditorOptions {
95
113
  content_types?: string[];
96
114
  row_cols?: boolean;
97
115
  utilities?: Record<string, unknown>;
98
- /** The style plugin's sections, and the options of the utilities it carries. */
99
- style?: StyleOptions;
116
+ /** The inline-style plugin's sections, and the options of the utilities it carries. */
117
+ inline_style?: InlineStyleOptions;
118
+ /** The tabs plugin: the variant of the tabs containers made new. */
119
+ tabs?: TabsOptions;
100
120
  elements?: { enabled?: boolean | 'auto'; selector?: string; auto?: boolean };
101
121
  custom_filter?: CustomFilter | CustomFilter[] | '';
102
122
  valid_col_sizes?: ColumnSize[];
@@ -328,6 +348,7 @@ export declare class GridEditor {
328
348
  createText(options: CreateTextOptions): HTMLElement | null;
329
349
  createElement(content: string | Node | ArrayLike<Node>, options?: CreateElementOptions): HTMLElement | null;
330
350
  createSection(options?: CreateSectionOptions): HTMLElement | null;
351
+ createContainer(type: 'tabs', options?: CreateTabsOptions): HTMLElement | null;
331
352
  createContainer(type: string, options?: Placement & Record<string, unknown>): HTMLElement | null;
332
353
  addTab(container: Target, options?: PaneOptions): HTMLElement | null;
333
354
  addAccordionItem(container: Target, options?: PaneOptions): HTMLElement | null;