@themarioga/grid-editor 7.3.0 → 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.
Files changed (103) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +34 -30
  3. package/UPGRADING.md +55 -0
  4. package/dist/grideditor.bundle.min.js +3 -3
  5. package/dist/grideditor.css +72 -6
  6. package/dist/grideditor.d.ts +24 -3
  7. package/dist/grideditor.esm.js +7 -58
  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 +7 -58
  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 +30 -6
  17. package/dist/locales/grideditor.es.js +30 -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 +393 -60
  39. package/dist/plugins/grideditor.style.js +387 -56
  40. package/dist/plugins/grideditor.style.min.js +4 -2
  41. package/dist/plugins/grideditor.style.min.js.map +4 -4
  42. package/dist/plugins/grideditor.summernote.min.js.map +2 -2
  43. package/dist/plugins/grideditor.tabs.esm.js +274 -6
  44. package/dist/plugins/grideditor.tabs.js +274 -6
  45. package/dist/plugins/grideditor.tabs.min.js +1 -1
  46. package/dist/plugins/grideditor.tabs.min.js.map +3 -3
  47. package/dist/plugins/grideditor.tinymce.esm.js +1 -8
  48. package/dist/plugins/grideditor.tinymce.js +1 -8
  49. package/dist/plugins/grideditor.tinymce.min.js +1 -1
  50. package/dist/plugins/grideditor.tinymce.min.js.map +3 -3
  51. package/docs/events.md +4 -6
  52. package/docs/locale-keys.md +35 -13
  53. package/docs/plugins.md +15 -35
  54. package/example/ckeditor.html +0 -5
  55. package/example/containers.html +64 -2
  56. package/example/index.html +5 -4
  57. package/example/style.html +16 -4
  58. package/example/utilities.html +12 -11
  59. package/package.json +1 -1
  60. package/src/js/dom.js +1 -2
  61. package/src/js/grideditor.jquery.js +7 -28
  62. package/src/js/grideditor.js +12 -81
  63. package/src/js/locales/grideditor.es.js +30 -6
  64. package/src/js/plugins/grideditor.alignment.js +21 -50
  65. package/src/js/plugins/grideditor.ckeditor.js +10 -23
  66. package/src/js/plugins/grideditor.style.js +18 -12
  67. package/src/js/plugins/grideditor.summernote.js +4 -5
  68. package/src/js/plugins/grideditor.tabs.js +311 -5
  69. package/src/js/plugins/grideditor.tinymce.js +4 -17
  70. package/src/js/style/accordion.js +14 -3
  71. package/src/js/style/display.js +229 -0
  72. package/src/js/style/drawerflow.js +118 -0
  73. package/src/js/style/flex.js +156 -0
  74. package/src/js/style/float.js +2 -4
  75. package/src/js/style/options.js +5 -20
  76. package/src/js/style/sections.js +42 -15
  77. package/src/js/style/spacing.js +2 -4
  78. package/src/js/style/sticky.js +39 -0
  79. package/src/js/style/textalign.js +2 -4
  80. package/src/js/text/grideditor.text.js +1 -1
  81. package/src/less/grideditor.less +68 -10
  82. package/types/grideditor.d.ts +24 -3
  83. package/dist/plugins/grideditor.float.esm.js +0 -43
  84. package/dist/plugins/grideditor.float.js +0 -47
  85. package/dist/plugins/grideditor.float.min.js +0 -1
  86. package/dist/plugins/grideditor.float.min.js.map +0 -7
  87. package/dist/plugins/grideditor.spacing.esm.js +0 -327
  88. package/dist/plugins/grideditor.spacing.js +0 -329
  89. package/dist/plugins/grideditor.spacing.min.js +0 -1
  90. package/dist/plugins/grideditor.spacing.min.js.map +0 -7
  91. package/dist/plugins/grideditor.textalign.esm.js +0 -49
  92. package/dist/plugins/grideditor.textalign.js +0 -53
  93. package/dist/plugins/grideditor.textalign.min.js +0 -1
  94. package/dist/plugins/grideditor.textalign.min.js.map +0 -7
  95. package/dist/plugins/grideditor.visibility.esm.js +0 -167
  96. package/dist/plugins/grideditor.visibility.js +0 -169
  97. package/dist/plugins/grideditor.visibility.min.js +0 -1
  98. package/dist/plugins/grideditor.visibility.min.js.map +0 -7
  99. package/src/js/plugins/grideditor.float.js +0 -29
  100. package/src/js/plugins/grideditor.spacing.js +0 -31
  101. package/src/js/plugins/grideditor.textalign.js +0 -27
  102. package/src/js/plugins/grideditor.visibility.js +0 -32
  103. package/src/js/style/visibility.js +0 -160
@@ -1,23 +1,24 @@
1
1
  /**
2
2
  * Alignment for grid-editor.
3
3
  *
4
- * A utility plugin: load this file after the editor and a row's columns can be
5
- * justified and aligned per breakpoint, and a column can align itself, with
6
- * Bootstrap's justify-content-{breakpoint}-*, align-items-{breakpoint}-* and
7
- * align-self-{breakpoint}-* classes. What it can ask the editor for is the
8
- * handle its factory is called with, described in docs/plugins.md.
4
+ * A utility plugin: load this file after the editor and a column can align
5
+ * itself in its row, per breakpoint, with Bootstrap's align-self-{breakpoint}-*
6
+ * classes. What it can ask the editor for is the handle its factory is called
7
+ * with, described in docs/plugins.md.
9
8
  *
10
9
  * <script src="dist/grideditor.min.js"></script>
11
10
  * <script src="dist/plugins/grideditor.alignment.min.js"></script>
12
11
  *
13
- * Fields only, in the settings panels: alignment is set now and then, not
12
+ * How a row justifies and aligns its columns - justify-content-* and
13
+ * align-items-* - is a flex container's, and the style plugin's Flex section
14
+ * edits it, on a row and on anything else made flex.
15
+ *
16
+ * A field only, in the settings panel: alignment is set now and then, not
14
17
  * clicked through, and a drawer is short of room.
15
18
  */
16
19
  import { GridEditor } from '../grideditor.js';
17
20
 
18
21
  Object.assign(GridEditor.locales.en, {
19
- 'utility.justify_content': 'Justify columns',
20
- 'utility.align_items': 'Align columns',
21
22
  'utility.align_self': 'Align self',
22
23
  });
23
24
 
@@ -26,54 +27,24 @@ var FLEX = {
26
27
  start: 'flex-start',
27
28
  end: 'flex-end',
28
29
  center: 'center',
29
- between: 'space-between',
30
- around: 'space-around',
31
- evenly: 'space-evenly',
32
30
  baseline: 'baseline',
33
31
  stretch: 'stretch',
34
32
  auto: 'auto',
35
33
  };
36
34
 
37
- /**
38
- * A family whose preview is its one css property. `none` is what the
39
- * property is when no class says anything, which a view has to show over a
40
- * wider breakpoint's class that is live in a wide window.
41
- */
42
- function family(definition) {
43
- return Object.assign({
44
- prefix: definition.name,
45
- preview: function(value) {
46
- var styles = {};
47
- styles[definition.name] = value === null ? definition.none : FLEX[value];
48
- return styles;
49
- },
50
- }, definition);
51
- }
52
-
53
35
  GridEditor.utilities.alignment = function() {
54
36
  return {
55
- families: [
56
- family({
57
- name: 'justify-content',
58
- labelKey: 'utility.justify_content',
59
- values: ['start', 'center', 'end', 'between', 'around', 'evenly'],
60
- appliesTo: ['row'],
61
- none: 'normal',
62
- }),
63
- family({
64
- name: 'align-items',
65
- labelKey: 'utility.align_items',
66
- values: ['start', 'center', 'end', 'baseline', 'stretch'],
67
- appliesTo: ['row'],
68
- none: 'normal',
69
- }),
70
- family({
71
- name: 'align-self',
72
- labelKey: 'utility.align_self',
73
- values: ['auto', 'start', 'center', 'end', 'baseline', 'stretch'],
74
- appliesTo: ['column'],
75
- none: 'auto',
76
- }),
77
- ],
37
+ families: [{
38
+ name: 'align-self',
39
+ prefix: 'align-self',
40
+ labelKey: 'utility.align_self',
41
+ values: ['auto', 'start', 'center', 'end', 'baseline', 'stretch'],
42
+ appliesTo: ['column'],
43
+
44
+ /** Without a class, auto: what the row's align-items says. */
45
+ preview: function(value) {
46
+ return { 'align-self': value === null ? 'auto' : FLEX[value] };
47
+ },
48
+ }],
78
49
  };
79
50
  };
@@ -11,15 +11,13 @@
11
11
  * <script src="dist/grideditor.min.js"></script>
12
12
  * <script src="dist/plugins/grideditor.ckeditor.min.js"></script>
13
13
  *
14
- * Up to 7.1 this was CKEditor 4, whose last open source release has known
15
- * vulnerabilities and no fixes to come. CKEditor 5 asks for a licenseKey:
16
- * 'GPL', or a commercial key, is the host's to pass in ckeditor.config.
14
+ * CKEditor 5 asks for a license key, GPL or a commercial one, which is the
15
+ * host's to pass in ckeditor.config.
17
16
  *
18
- * Up to 5.x the main bundle carried a copy of this file; since 6.0 it is
19
- * loaded on its own, like every plugin. It imports what every text editor
20
- * shares - text blocks, the Text button, createText, making the host's plain
21
- * content a text - from src/js/text/grideditor.text.js, which the build puts
22
- * in its classic script, installed once however many editors a page loads.
17
+ * It imports what every text editor shares - text blocks, the Text button,
18
+ * createText, making the host's plain content a text - from
19
+ * src/js/text/grideditor.text.js, which the build puts in its classic
20
+ * script, installed once however many editors a page loads.
23
21
  */
24
22
  import { GridEditor } from '../grideditor.js';
25
23
  import * as dom from '../dom.js';
@@ -34,8 +32,8 @@ Object.assign(GridEditor.locales.en, {
34
32
  var INITIAL_CONTENT = '<p>Lorem initius... </p>';
35
33
 
36
34
  // What CKEditor 5 is started with when the host's config does not say. It
37
- // has no plugins of its own, so these are what a text can do: much what the
38
- // CKEditor 4 build 7.1 used offered. Names, looked up on window.CKEDITOR.
35
+ // has no plugins of its own, so these are what a text can do. Names, looked
36
+ // up on window.CKEDITOR.
39
37
  var PLUGINS = [
40
38
  'Essentials', 'Autoformat', 'Paragraph', 'Heading', 'Bold', 'Italic', 'Underline', 'Strikethrough',
41
39
  'Link', 'AutoLink', 'List', 'BlockQuote', 'Indent', 'HorizontalLine', 'Table', 'TableToolbar',
@@ -122,9 +120,6 @@ GridEditor.texts.ckeditor = function(ge) {
122
120
 
123
121
  var configuration = Object.assign({}, DEFAULTS, userConfig);
124
122
  configuration.plugins = pluginsFor(ge, userConfig.plugins || PLUGINS);
125
- // on.instanceReady is CKEditor 4's, and CKEditor 5 has no
126
- // such option: this file calls it, as it did in 7.1
127
- delete configuration.on;
128
123
 
129
124
  var ticket = {};
130
125
  starting.set(contentArea, ticket);
@@ -144,13 +139,6 @@ GridEditor.texts.ckeditor = function(ge) {
144
139
  starting.delete(contentArea);
145
140
  editors.set(contentArea, editor);
146
141
 
147
- // Call the original instanceReady function, if one was
148
- // passed in the config: 7.1's, with CKEditor 4's event
149
- var callback = userConfig.on && userConfig.on.instanceReady;
150
- if (callback) {
151
- callback.call(editor, { editor: editor });
152
- }
153
-
154
142
  // The editor owns what is inside the content area now,
155
143
  // so the grid editor is told to put its own furniture back
156
144
  ge.textReady(contentArea);
@@ -173,9 +161,8 @@ GridEditor.texts.ckeditor = function(ge) {
173
161
  contentAreas.filter(function(contentArea) {
174
162
  return dom.hasClass(contentArea, 'active');
175
163
  }).forEach(function(contentArea) {
176
- // This content area's editor, and no other: up to 5.x
177
- // closing one content area destroyed every CKEditor on
178
- // the page, other editors' and the host's own included
164
+ // This content area's editor, and no other: the other
165
+ // CKEditors on the page, the host's own included, stay
179
166
  var editor = editors.get(contentArea);
180
167
  editors.delete(contentArea);
181
168
  // One still starting sees this and goes as it is ready
@@ -4,7 +4,7 @@
4
4
  * A utility plugin: load this file after the editor and every row, column,
5
5
  * container, pane, element and section gets a Style accordion in its
6
6
  * settings panel - size, spacing, border, background, text, typography,
7
- * display, position and free css - written to the node's own style
7
+ * display, flex, position and free css - written to the node's own style
8
8
  * attribute, with Bootstrap's classes for each section as chips that put
9
9
  * them in the classes field. What it can ask the editor for is the handle
10
10
  * its factory is called with, described in docs/plugins.md.
@@ -12,23 +12,26 @@
12
12
  * <script src="dist/grideditor.min.js"></script>
13
13
  * <script src="dist/plugins/grideditor.style.min.js"></script>
14
14
  *
15
- * It carries what the spacing, textalign, visibility and float plugins
16
- * were up to 7.2: their per breakpoint fields are in its sections rather
17
- * than in Responsive, and those four files are deprecated. Loaded beside
18
- * it they stand down, and their names in the plugins setting ask for it.
15
+ * It also edits spacing, text alignment, display, flex, sticky and float,
16
+ * Bootstrap's responsive classes, per breakpoint in its sections. On a node
17
+ * that is flex or grid, the drawer leaves the flow, so the canvas lays the
18
+ * content out as the page will.
19
19
  *
20
20
  * Where the settings panel has no room for it - a popover, inline in the
21
21
  * drawer - the accordion opens in a dialog, from a Style button.
22
22
  *
23
23
  * style.sections chooses the sections, and in each the properties and
24
- * whether it has a catalog; style.spacing and style.visibility are what
25
- * utilities.spacing and utilities.visibility were.
24
+ * whether it has a catalog; style.spacing is the options of spacing, whose
25
+ * scale the gaps share, and style.visibility those of the eye.
26
26
  */
27
27
  import { GridEditor } from '../grideditor.js';
28
28
  import { spacingPart } from '../style/spacing.js';
29
29
  import { textalignPart } from '../style/textalign.js';
30
- import { visibilityPart } from '../style/visibility.js';
30
+ import { displayPart } from '../style/display.js';
31
+ import { flexPart } from '../style/flex.js';
31
32
  import { floatPart } from '../style/float.js';
33
+ import { stickyPart } from '../style/sticky.js';
34
+ import { drawerflowPart } from '../style/drawerflow.js';
32
35
  import { resolveOptions } from '../style/options.js';
33
36
  import { createAccordion } from '../style/accordion.js';
34
37
 
@@ -42,6 +45,7 @@ Object.assign(GridEditor.locales.en, {
42
45
  'style.section_text': 'Text',
43
46
  'style.section_typography': 'Typography',
44
47
  'style.section_display': 'Display',
48
+ 'style.section_flex': 'Flex',
45
49
  'style.section_position': 'Position',
46
50
  'style.section_custom': 'Custom css',
47
51
  'style.all_sizes': 'Applies to every size',
@@ -102,7 +106,8 @@ Object.assign(GridEditor.locales.en, {
102
106
  'style.prop_z_index': 'Z-index',
103
107
  });
104
108
 
105
- var PARTS = ['spacing', 'textalign', 'visibility', 'float'];
109
+ // The drawer's part last: it asks the browser how the others left the node
110
+ var PARTS = ['spacing', 'textalign', 'display', 'flex', 'float', 'sticky', 'drawerflow'];
106
111
 
107
112
  GridEditor.utilities.style = function(ge) {
108
113
 
@@ -110,8 +115,11 @@ GridEditor.utilities.style = function(ge) {
110
115
  var parts = {
111
116
  spacing: spacingPart(ge, options.spacing),
112
117
  textalign: textalignPart(ge),
113
- visibility: visibilityPart(ge, options.visibility),
118
+ display: displayPart(ge, options.visibility),
119
+ flex: flexPart(ge, options.spacing),
114
120
  float: floatPart(ge),
121
+ sticky: stickyPart(ge),
122
+ drawerflow: drawerflowPart(ge),
115
123
  };
116
124
  var accordions = []; // { node, accordion } for every panel built since the last init
117
125
  var context = {
@@ -175,5 +183,3 @@ GridEditor.utilities.style = function(ge) {
175
183
  },
176
184
  };
177
185
  };
178
-
179
- GridEditor.utilities.style.replaces = PARTS;
@@ -16,11 +16,10 @@
16
16
  * nothing else. Towards the editor it is plain DOM, like every plugin, and
17
17
  * it needs no jQuery adapter.
18
18
  *
19
- * Up to 5.x the main bundle carried a copy of this file; since 6.0 it is
20
- * loaded on its own, like every plugin. It imports what every text editor
21
- * shares - text blocks, the Text button, createText, making the host's plain
22
- * content a text - from src/js/text/grideditor.text.js, which the build puts
23
- * in its classic script, installed once however many editors a page loads.
19
+ * It imports what every text editor shares - text blocks, the Text button,
20
+ * createText, making the host's plain content a text - from
21
+ * src/js/text/grideditor.text.js, which the build puts in its classic
22
+ * script, installed once however many editors a page loads.
24
23
  */
25
24
  import { GridEditor } from '../grideditor.js';
26
25
  import * as dom from '../dom.js';
@@ -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
  });