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

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.
@@ -100,8 +100,11 @@
100
100
  asked to do any of it, so there is no backdrop and no focus
101
101
  trap to fight with. Double click a tab or item label to
102
102
  rename it, and a card’s header and footer the same
103
- way. The last row shows two levels: tabs inside an
104
- accordion item.
103
+ way. The row after the popup shows two levels: tabs inside
104
+ an accordion item. A tabs container's gear has a Tabs
105
+ section for its variant - tabs, pills or underline, full
106
+ width, alignment, and vertical from a breakpoint up - and
107
+ the last row has two of them.
105
108
  </p>
106
109
  <button class="btn btn-sm btn-secondary" id="output" type="button">Show the html this produces</button>
107
110
  <button class="btn btn-sm btn-primary" id="authored" type="button">Open that html as a real page</button>
@@ -239,6 +242,65 @@
239
242
  </div>
240
243
  </div>
241
244
 
245
+ <div class="row">
246
+ <div class="col-lg-6">
247
+ <div data-ge-container="tabs" class="d-md-flex align-items-md-start">
248
+ <ul class="nav nav-pills flex-md-column me-md-3" role="tablist" aria-orientation="vertical">
249
+ <li class="nav-item" role="presentation">
250
+ <button class="nav-link active" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#example-vtab-one" aria-controls="example-vtab-one">Profile</button>
251
+ </li>
252
+ <li class="nav-item" role="presentation">
253
+ <button class="nav-link" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#example-vtab-two" aria-controls="example-vtab-two">Settings</button>
254
+ </li>
255
+ </ul>
256
+ <div class="tab-content">
257
+ <div class="tab-pane fade show active" id="example-vtab-one" role="tabpanel" tabindex="0">
258
+ <div class="row"><div class="col-lg-12">
259
+ <div class="ge-content" data-ge-content-type="tinymce"><p>Pills, vertical from tablet up: open the container&rsquo;s gear and look in Tabs.</p></div>
260
+ </div></div>
261
+ </div>
262
+ <div class="tab-pane fade" id="example-vtab-two" role="tabpanel" tabindex="0">
263
+ <div class="row"><div class="col-lg-12">
264
+ <div class="ge-content" data-ge-content-type="tinymce"><p>On a phone the pills are a row above the panes.</p></div>
265
+ </div></div>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ <div class="col-lg-6">
271
+ <div data-ge-container="tabs">
272
+ <ul class="nav nav-underline nav-fill" role="tablist">
273
+ <li class="nav-item" role="presentation">
274
+ <button class="nav-link active" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#example-utab-one" aria-controls="example-utab-one">Week</button>
275
+ </li>
276
+ <li class="nav-item" role="presentation">
277
+ <button class="nav-link" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#example-utab-two" aria-controls="example-utab-two">Month</button>
278
+ </li>
279
+ <li class="nav-item" role="presentation">
280
+ <button class="nav-link" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#example-utab-three" aria-controls="example-utab-three">Year</button>
281
+ </li>
282
+ </ul>
283
+ <div class="tab-content">
284
+ <div class="tab-pane fade show active" id="example-utab-one" role="tabpanel" tabindex="0">
285
+ <div class="row"><div class="col-lg-12">
286
+ <div class="ge-content" data-ge-content-type="tinymce"><p>Underline, filling the width.</p></div>
287
+ </div></div>
288
+ </div>
289
+ <div class="tab-pane fade" id="example-utab-two" role="tabpanel" tabindex="0">
290
+ <div class="row"><div class="col-lg-12">
291
+ <div class="ge-content" data-ge-content-type="tinymce"><p>The second.</p></div>
292
+ </div></div>
293
+ </div>
294
+ <div class="tab-pane fade" id="example-utab-three" role="tabpanel" tabindex="0">
295
+ <div class="row"><div class="col-lg-12">
296
+ <div class="ge-content" data-ge-content-type="tinymce"><p>The third.</p></div>
297
+ </div></div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
303
+
242
304
  </div> <!-- /#myGrid -->
243
305
  </div> <!-- /.container -->
244
306
 
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": "8.0.0-beta.2",
36
+ "version": "8.0.0-beta.3",
37
37
  "main": "dist/grideditor.js",
38
38
  "module": "dist/grideditor.esm.js",
39
39
  "types": "dist/grideditor.d.ts",
@@ -74,6 +74,23 @@ GridEditor.locales.es = {
74
74
  'container.add_tabs': 'Pestañas',
75
75
  'container.add_tab': 'Añadir pestaña',
76
76
  'container.tab_label': 'Pestaña {number}',
77
+ 'container.tabs_section': 'Pestañas',
78
+ 'container.tabs_style': 'Estilo',
79
+ 'container.tabs_style_tabs': 'Pestañas',
80
+ 'container.tabs_style_pills': 'Pills',
81
+ 'container.tabs_style_underline': 'Subrayado',
82
+ 'container.tabs_width': 'Ancho',
83
+ 'container.tabs_width_natural': 'Natural',
84
+ 'container.tabs_width_fill': 'Rellenar',
85
+ 'container.tabs_width_justified': 'Justificado',
86
+ 'container.tabs_align': 'Alineación',
87
+ 'container.tabs_align_start': 'Inicio',
88
+ 'container.tabs_align_center': 'Centro',
89
+ 'container.tabs_align_end': 'Fin',
90
+ 'container.tabs_layout': 'Disposición',
91
+ 'container.tabs_layout_horizontal': 'Horizontal',
92
+ 'container.tabs_layout_vertical': 'Vertical',
93
+ 'container.tabs_layout_vertical_from': 'Vertical desde {breakpoint}',
77
94
  'container.add_accordion': 'Acordeón',
78
95
  'container.add_accordion_item': 'Añadir apartado',
79
96
  'container.accordion_label': 'Apartado {number}',
@@ -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
  });
@@ -714,6 +714,34 @@
714
714
  }
715
715
  }
716
716
 
717
+ /* A tabs container laid out vertically from a breakpoint: Bootstrap's
718
+ classes answer to the window, and a breakpoint view narrows the canvas
719
+ instead, so the tabs plugin says which layout the view has and that is
720
+ what shows. The selectors outweigh Bootstrap's !important utilities.
721
+ Vertical, the container wraps, so its drawer is a line of its own
722
+ above the strip and the panes */
723
+ [data-ge-tabs-layout="vertical"] {
724
+ display: flex !important;
725
+ flex-wrap: wrap;
726
+ align-items: flex-start !important;
727
+
728
+ > .ge-container-drawer {
729
+ flex-shrink: 0;
730
+ }
731
+ > .nav {
732
+ flex-direction: column !important;
733
+ margin-right: 1rem !important;
734
+ }
735
+ }
736
+ [data-ge-tabs-layout="horizontal"] {
737
+ display: block !important;
738
+
739
+ > .nav {
740
+ flex-direction: row !important;
741
+ margin-right: 0 !important;
742
+ }
743
+ }
744
+
717
745
  /* The label is edited in place, so it needs to look like it can be */
718
746
  .ge-pane-label {
719
747
  &:hover {
@@ -59,6 +59,24 @@ export type StyleSectionKey = 'size' | 'spacing' | 'border' | 'background' | 'te
59
59
  /** A section on or off, or on with some of its properties and without its catalog. */
60
60
  export type StyleSection = boolean | { properties?: string[]; catalog?: boolean };
61
61
 
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
+
62
80
  /** The style plugin's settings. */
63
81
  export interface StyleOptions {
64
82
  /** Every section is on unless it is turned off here. */
@@ -97,6 +115,8 @@ export interface GridEditorOptions {
97
115
  utilities?: Record<string, unknown>;
98
116
  /** The style plugin's sections, and the options of the utilities it carries. */
99
117
  style?: StyleOptions;
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;