@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.
- package/CHANGELOG.md +16 -0
- package/README.md +7 -0
- package/dist/grideditor.bundle.min.js +2 -2
- package/dist/grideditor.css +25 -0
- package/dist/grideditor.d.ts +21 -0
- package/dist/grideditor.esm.js +1 -1
- package/dist/grideditor.js +1 -1
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/grideditor.min.js +1 -1
- package/dist/locales/grideditor.es.esm.js +17 -0
- package/dist/locales/grideditor.es.js +17 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +2 -2
- package/dist/plugins/grideditor.tabs.esm.js +274 -6
- package/dist/plugins/grideditor.tabs.js +274 -6
- package/dist/plugins/grideditor.tabs.min.js +1 -1
- package/dist/plugins/grideditor.tabs.min.js.map +3 -3
- package/docs/locale-keys.md +17 -0
- package/example/containers.html +64 -2
- package/package.json +1 -1
- package/src/js/locales/grideditor.es.js +17 -0
- package/src/js/plugins/grideditor.tabs.js +311 -5
- package/src/less/grideditor.less +28 -0
- package/types/grideditor.d.ts +21 -0
package/example/containers.html
CHANGED
|
@@ -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
|
|
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’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.
|
|
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 =
|
|
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, '
|
|
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
|
|
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.
|
|
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.
|
|
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
|
});
|
package/src/less/grideditor.less
CHANGED
|
@@ -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 {
|
package/types/grideditor.d.ts
CHANGED
|
@@ -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;
|