@lemonadejs/tabs 2.3.0 → 5.2.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.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ declare namespace Tabs {
10
10
  interface Item {
11
11
  // Reference to an external DOM
12
12
  el: HTMLElement,
13
+ /** Material icon keyword */
14
+ icon?: string;
13
15
  // Tab header
14
16
  title: string,
15
17
  // HTML template
@@ -22,13 +24,21 @@ declare namespace Tabs {
22
24
  /** Selected tab */
23
25
  selected?: number;
24
26
  /** Tabs position */
25
- position?: 'center' | undefined;
27
+ position?: 'default' | 'center' | 'bottom' | undefined;
26
28
  /** Activate round borders */
27
29
  round?: boolean;
28
- /** On open event */
29
- onopen?: (instance: object, index: number) => void;
30
30
  /** Allow to create new tab button */
31
31
  allowCreate?: boolean;
32
+ /** Change tabs */
33
+ onchange?: (instance: object, value: number) => void;
34
+ /** On open event */
35
+ onopen?: (instance: object, index: number) => void;
36
+ /** Before create new tab */
37
+ onbeforecreate?: (instance: object, item: Item, position: number) => boolean | void;
38
+ /** On create new tab event */
39
+ oncreate?: (instance: object, item: Item, position: number) => void;
40
+ /** On change tab position event */
41
+ onchangeposition: (instance: object, fromIndex: number, toIndex: number) => void;
32
42
  }
33
43
 
34
44
  interface Instance {
@@ -42,8 +52,8 @@ declare namespace Tabs {
42
52
  position?: 'center' | undefined;
43
53
  /** Activate round borders */
44
54
  round?: boolean;
45
- /** On open event */
46
- onopen?: (instance: object, index: number) => void;
55
+ /** Allow to create new tab button */
56
+ allowCreate?: boolean;
47
57
  }
48
58
  }
49
59
 
package/dist/index.js CHANGED
@@ -8,12 +8,33 @@ if (! lemonade && typeof (require) === 'function') {
8
8
  global.Tabs = factory();
9
9
  }(this, (function () {
10
10
 
11
+ class CustomEvents extends Event {
12
+ constructor(type, props, options) {
13
+ super(type, {
14
+ bubbles: true,
15
+ composed: true,
16
+ ...options,
17
+ });
18
+
19
+ if (props) {
20
+ for (const key in props) {
21
+ // Avoid assigning if property already exists anywhere on `this`
22
+ if (! (key in this)) {
23
+ this[key] = props[key];
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+
11
30
  // Dispatcher
12
- const Dispatch = function(type){
13
- if (typeof this[type] === 'function') {
14
- let args = Array.from(arguments);
15
- args.shift();
16
- this[type](...args)
31
+ const Dispatch = function(method, type, options) {
32
+ // Try calling the method directly if provided
33
+ if (typeof method === 'function') {
34
+ let a = Object.values(options);
35
+ return method(...a);
36
+ } else if (this.tagName) {
37
+ this.dispatchEvent(new CustomEvents(type, options));
17
38
  }
18
39
  }
19
40
 
@@ -37,22 +58,122 @@ if (! lemonade && typeof (require) === 'function') {
37
58
  }
38
59
  }
39
60
 
40
- const Tabs = function(children) {
61
+ const sorting = function(el, options) {
62
+ const obj = {};
63
+
64
+ let dragElement = null;
65
+
66
+ el.addEventListener('dragstart', function(e) {
67
+ let target = e.target;
68
+ if (target.nodeType === 3) {
69
+ if (target.parentNode.getAttribute('draggable') === 'true') {
70
+ target = target.parentNode;
71
+ } else {
72
+ e.preventDefault();
73
+ e.stopPropagation();
74
+ return;
75
+ }
76
+ }
77
+
78
+ if (target.getAttribute('draggable') === 'true') {
79
+ let position = Array.prototype.indexOf.call(target.parentNode.children, target);
80
+ dragElement = {
81
+ element: target,
82
+ o: position,
83
+ d: position
84
+ }
85
+ target.style.opacity = '0.25';
86
+ e.dataTransfer.setDragImage(target,0,0);
87
+ }
88
+ });
89
+
90
+ el.addEventListener('dragover', function(e) {
91
+ e.preventDefault();
92
+
93
+ if (dragElement && getElement(e.target) && e.target.getAttribute('draggable') == 'true' && dragElement.element != e.target) {
94
+ let element = e.target.clientWidth / 2 > e.offsetX ? e.target : e.target.nextSibling;
95
+ e.target.parentNode.insertBefore(dragElement.element, element);
96
+ dragElement.d = Array.prototype.indexOf.call(e.target.parentNode.children, dragElement.element);
97
+ }
98
+ });
99
+
100
+ el.addEventListener('dragleave', function(e) {
101
+ e.preventDefault();
102
+ });
103
+
104
+ el.addEventListener('dragend', function(e) {
105
+ e.preventDefault();
106
+
107
+ if (dragElement) {
108
+ let element = dragElement.o < dragElement.d ? e.target.parentNode.children[dragElement.o] : e.target.parentNode.children[dragElement.o].nextSibling
109
+ e.target.parentNode.insertBefore(dragElement.element, element);
110
+ dragElement.element.style.opacity = '';
111
+ dragElement = null;
112
+ }
113
+ });
114
+
115
+ el.addEventListener('drop', function(e) {
116
+ e.preventDefault();
117
+
118
+ if (dragElement) {
119
+ if (dragElement.o !== dragElement.d) {
120
+ if (typeof(options.ondrop) == 'function') {
121
+ options.ondrop(el, dragElement.o, dragElement.d, dragElement.element, e.target, e);
122
+ }
123
+ }
124
+
125
+ dragElement.element.style.opacity = '';
126
+ dragElement = null;
127
+ }
128
+ });
129
+
130
+ const getElement = function(element) {
131
+ var sorting = false;
132
+
133
+ function path (element) {
134
+ if (element === el) {
135
+ sorting = true;
136
+ }
137
+
138
+ if (! sorting) {
139
+ path(element.parentNode);
140
+ }
141
+ }
142
+
143
+ path(element);
144
+
145
+ return sorting;
146
+ }
147
+
148
+ for (let i = 0; i < el.children.length; i++) {
149
+ if (! el.children[i].hasAttribute('draggable')) {
150
+ el.children[i].setAttribute('draggable', 'true');
151
+ }
152
+ }
153
+
154
+ return el;
155
+ }
156
+
157
+ const Tabs = function(children, { onchange, onload }) {
41
158
  let self = this
42
159
 
160
+ // Event
161
+ let change = self.onchange;
162
+ self.onchange = null;
163
+
43
164
  // Add new tab
44
165
  let createButton;
45
166
 
46
167
  // Get the references from the root web component
47
168
  let root;
169
+ let template = '';
48
170
  if (this.tagName) {
49
171
  root = this;
50
172
  } else {
51
173
  // References from LemonadeJS
52
174
  if (typeof(children) === 'string') {
53
175
  // Version 4
54
- root = document.createElement('div');
55
- root.innerHTML = children;
176
+ template = children;
56
177
  } else if (children && children.length) {
57
178
  // Version 5
58
179
  root = children;
@@ -66,14 +187,7 @@ if (! lemonade && typeof (require) === 'function') {
66
187
  // Process the data
67
188
  if (self.data) {
68
189
  for (let i = 0; i < self.data.length; i++) {
69
- if (self.data[i].el) {
70
- if (! self.data[i].title) {
71
- self.data[i].title = self.data[i].el.getAttribute('title');
72
- }
73
- if (! self.data[i].selected) {
74
- self.data[i].selected = self.data[i].el.getAttribute('selected');
75
- }
76
- } else {
190
+ if (! self.data[i].el) {
77
191
  // Create element
78
192
  self.data[i].el = document.createElement('div');
79
193
  // Create from content
@@ -84,7 +198,9 @@ if (! lemonade && typeof (require) === 'function') {
84
198
  }
85
199
  }
86
200
 
87
- const select = function (index) {
201
+ let props = ['title', 'selected', 'data-icon'];
202
+
203
+ const select = function(index) {
88
204
  // Make sure the index is a number
89
205
  index = parseInt(index);
90
206
  // Do not select tabs that does not exist
@@ -100,7 +216,20 @@ if (! lemonade && typeof (require) === 'function') {
100
216
 
101
217
  const init = function(selected) {
102
218
  let tabs = [];
219
+
103
220
  for (let i = 0; i < self.data.length; i++) {
221
+ // Extract meta information from the DOM
222
+ if (props) {
223
+ props.forEach((prop) => {
224
+ let short = prop.replace('data-', '');
225
+ if (! self.data[i][short]) {
226
+ let ret = self.data[i].el.getAttribute(prop);
227
+ if (ret != null) {
228
+ self.data[i][short] = ret;
229
+ }
230
+ }
231
+ });
232
+ }
104
233
  // Create tabs object
105
234
  tabs[i] = {
106
235
  title: self.data[i].title,
@@ -109,26 +238,68 @@ if (! lemonade && typeof (require) === 'function') {
109
238
  if (self.data[i].selected) {
110
239
  selected = i;
111
240
  }
241
+ if (self.data[i].icon) {
242
+ tabs[i].icon = self.data[i].icon;
243
+ }
112
244
 
113
- self.root.appendChild(self.data[i].el)
245
+ self.root.appendChild(self.data[i].el);
114
246
  }
247
+
115
248
  // Create headers
116
249
  self.tabs = tabs;
250
+
117
251
  // Default selected
118
252
  if (typeof(selected) !== 'undefined') {
119
253
  self.selected = selected;
120
254
  }
121
- // Add create new tab button
122
- if (createButton) {
123
- self.headers.appendChild(createButton);
255
+
256
+ if (props) {
257
+ // Add create new tab button
258
+ if (createButton) {
259
+ self.headers.appendChild(createButton);
260
+ }
261
+ // Add sorting
262
+ sorting(self.el.firstChild.firstChild, {
263
+ ondrop: (el, fromIndex, toIndex) => {
264
+ // Remove the item from its original position
265
+ const [movedItem] = self.data.splice(fromIndex, 1);
266
+ // Insert it into the new position
267
+ self.data.splice(toIndex, 0, movedItem);
268
+ // Make sure correct order
269
+ for (let i = 0; i < self.data.length; i++) {
270
+ self.root.appendChild(self.data[i].el);
271
+ }
272
+ // Select new position
273
+ self.selected = toIndex;
274
+ // Dispatch event
275
+ Dispatch.call(self, self.onchangeposition, 'changeposition', {
276
+ instance: self,
277
+ fromIndex: fromIndex,
278
+ toIndex: toIndex,
279
+ });
280
+ }
281
+ })
124
282
  }
283
+
284
+ props = null;
125
285
  }
126
286
 
127
- self.onload = function () {
128
- init(self.selected || 0);
287
+ const create = function() {
288
+ // Create a new item
289
+ self.create({ title: 'Untitled' }, null, true);
129
290
  }
130
291
 
131
- self.keydown = function(e, s) {
292
+ const open = function(e) {
293
+ if (e.target.tagName === 'LI') {
294
+ // Avoid select something already selected
295
+ let index = Array.prototype.indexOf.call(e.target.parentNode.children, e.target);
296
+ if (index !== self.selected) {
297
+ self.selected = index;
298
+ }
299
+ }
300
+ }
301
+
302
+ const keydown = function(e, s) {
132
303
  let index = null;
133
304
  if (e.key === 'Enter') {
134
305
  self.click(e, s);
@@ -150,14 +321,32 @@ if (! lemonade && typeof (require) === 'function') {
150
321
  }
151
322
  }
152
323
 
153
- self.open = function (e) {
154
- if (e.target.tagName === 'LI') {
155
- // Avoid select something already selected
156
- let index = Array.prototype.indexOf.call(e.target.parentNode.children, e.target);
157
- if (index !== self.selected) {
158
- self.selected = index;
159
- }
324
+ onload(() => {
325
+ if (template) {
326
+ extract(self.root, self);
327
+ }
328
+
329
+ init(self.selected || 0);
330
+ })
331
+
332
+ onchange((property) => {
333
+ if (property === 'selected') {
334
+ select(self.selected);
335
+
336
+ Dispatch.call(self, self.onopen, 'open', {
337
+ instance: self,
338
+ selected: self.selected,
339
+ });
340
+
341
+ Dispatch.call(self, change, 'change', {
342
+ instance: self,
343
+ value: self.selected,
344
+ });
160
345
  }
346
+ })
347
+
348
+ self.open = function (index) {
349
+ self.selected = index;
161
350
  }
162
351
 
163
352
  self.create = function(item, position, select) {
@@ -165,6 +354,17 @@ if (! lemonade && typeof (require) === 'function') {
165
354
  if (typeof(item) !== 'object') {
166
355
  console.error('Item must be an object');
167
356
  } else {
357
+
358
+ let ret = Dispatch.call(self, self.onbeforecreate, 'beforecreate', {
359
+ instance: self,
360
+ item: item,
361
+ position: position,
362
+ });
363
+
364
+ if (ret === false) {
365
+ return false;
366
+ }
367
+
168
368
  // Create DOM
169
369
  item.el = document.createElement('div');
170
370
  // Create from content
@@ -188,30 +388,27 @@ if (! lemonade && typeof (require) === 'function') {
188
388
  } else {
189
389
  init(self.selected);
190
390
  }
191
- }
192
- }
193
-
194
- self.click = function() {
195
- // Create a new item
196
- self.create({ title: 'Untitled' }, null, true);
197
- }
198
391
 
199
- self.onchange = function (property) {
200
- if (property === 'selected') {
201
- select(self.selected);
392
+ self.tabs.forEach(item => {
393
+ item.el.setAttribute('draggable', 'true');
394
+ })
202
395
 
203
- Dispatch.call(self, 'onopen', self, self.selected);
396
+ Dispatch.call(self, self.oncreate, 'create', {
397
+ instance: self,
398
+ item: item,
399
+ position: position,
400
+ });
204
401
  }
205
402
  }
206
403
 
207
404
  self.allowCreate = !! self.allowCreate;
208
405
 
209
- return `<div class="lm-tabs" data-position="{{self.position}}" data-round="{{self.round}}">
406
+ return render => render`<div class="lm-tabs" data-position="{{self.position}}" data-round="{{self.round}}">
210
407
  <div role="tabs" class="lm-tabs-headers">
211
- <ul :ref="self.headers" :loop="self.tabs" :selected="self.selected" onclick="self.open" onkeydown="self.keydown" onfocusin="self.open"><li class="lm-tab" tabindex="0" role="tab">{{self.title}}</li></ul>
212
- <div data-visible="{{self.allowCreate}}" class="lm-tabs-insert-tab material-icons" role="insert-tab" onclick="self.click">add</div>
408
+ <ul :ref="self.headers" :loop="self.tabs" :selected="self.selected" onclick="${open}" onkeydown="${keydown}" onfocusin="${open}"><li class="lm-tab" tabindex="0" role="tab" data-icon="{{self.icon}}">{{self.title}}</li></ul>
409
+ <div data-visible="{{self.allowCreate}}" class="lm-tabs-insert-button" role="insert-tab" onclick="${create}">add</div>
213
410
  </div>
214
- <div :ref="self.root" class="lm-tabs-content"></div>
411
+ <div :ref="self.root" class="lm-tabs-content">${template}</div>
215
412
  </div>`
216
413
  }
217
414
 
package/dist/style.css CHANGED
@@ -1,78 +1,104 @@
1
+ .lm-tabs[data-position="bottom"] {
2
+ display: flex;
3
+ justify-content: center;
4
+ flex-direction: column-reverse;
5
+ }
6
+
1
7
  .lm-tabs .lm-tabs-content > div {
2
- padding: 6px;
8
+ padding: 15px 5px;
3
9
  }
4
10
 
5
11
  .lm-tabs .lm-tabs-headers {
6
- display: flex;
7
- align-items: center;
8
- width: 100%;
12
+ display: flex;
13
+ align-items: center;
14
+ width: 100%;
9
15
  }
10
16
 
11
17
  .lm-tabs[data-position="center"] .lm-tabs-headers {
12
- margin: 0 auto;
13
- justify-content: center;
18
+ margin: 0 auto;
19
+ justify-content: center;
14
20
  }
15
21
 
16
22
  .lm-tabs .lm-tabs-headers > ul {
17
- list-style-type: none;
18
- display: flex;
19
- margin: 0;
20
- padding: 0;
21
- align-items: center;
23
+ list-style-type: none;
24
+ display: flex;
25
+ margin: 0;
26
+ padding: 0;
27
+ align-items: center;
22
28
  }
23
29
 
24
30
  .lm-tabs .lm-tabs-headers > ul > li {
25
- cursor: pointer;
26
- user-select: none;
27
- padding: 4px 24px;
28
- border: 1px solid #ccc;
29
- background-position: center;
30
- transition: background 0.8s;
31
+ cursor: pointer;
32
+ user-select: none;
33
+ padding: 4px 24px;
34
+ border: 1px solid #ccc;
35
+ background-position: center;
36
+ transition: background 0.8s;
37
+ display: flex;
38
+ flex-direction: column;
39
+ align-items: center;
31
40
  }
32
41
 
33
42
  .lm-tabs .lm-tabs-headers > ul > li.selected {
34
- background-color: #eee;
35
- color: #000;
43
+ background-color: #eee;
44
+ color: #000;
45
+ }
46
+
47
+ .lm-tabs .lm-tabs-headers > ul > li[data-icon]:before {
48
+ content: attr(data-icon);
49
+ font-family: "Material Symbols Outlined", "Material Icons", "FontAwesome";
50
+ font-size: 24px;
51
+ width: 24px;
52
+ height: 24px;
53
+ text-align: center;
54
+ color: var(--lm-icon-color, #777);
55
+ margin-bottom: 5px;
36
56
  }
37
57
 
38
58
  .lm-tabs[data-round="true"] .lm-tabs-headers > ul > li:first-of-type {
39
- border-top-left-radius: 3px;
40
- border-bottom-left-radius: 3px;
59
+ border-top-left-radius: 3px;
60
+ border-bottom-left-radius: 3px;
41
61
  }
42
62
 
43
63
  .lm-tabs[data-round="true"] .lm-tabs-headers > ul > li:last-of-type {
44
- border-top-right-radius: 3px;
45
- border-bottom-right-radius: 3px;
64
+ border-top-right-radius: 3px;
65
+ border-bottom-right-radius: 3px;
46
66
  }
47
67
 
48
68
  .lm-tabs .lm-tabs-headers > ul > li:not(:first-child) {
49
- border-left: 1px solid transparent;
69
+ border-left: 1px solid transparent;
50
70
  }
51
71
 
52
72
  .lm-tabs .lm-tabs-headers > ul > li:hover {
53
- background: #eee radial-gradient(circle, transparent 1%, #eee 1%) center/15000%;
73
+ background: #eee radial-gradient(circle, transparent 1%, #eee 1%) center/15000%;
54
74
  }
55
75
 
56
76
  .lm-tabs .lm-tabs-headers > ul > li:active {
57
- background-color: #ddd;
58
- background-size: 100%;
59
- transition: background 0s;
77
+ background-color: #ddd;
78
+ background-size: 100%;
79
+ transition: background 0s;
60
80
  }
61
81
 
62
82
  .lm-tabs .lm-tabs-content > div {
63
- display: none;
83
+ display: none;
64
84
  }
65
85
 
66
86
  .lm-tabs .lm-tabs-content > div.selected {
67
- display: block;
87
+ display: block;
68
88
  }
69
89
 
70
- .lm-tabs-insert-tab {
71
- margin-left: 5px;
72
- color: #555;
73
- cursor: pointer;
90
+ .lm-tabs-insert-button {
91
+ margin-left: 5px;
92
+ color: #555;
93
+ cursor: pointer;
94
+ font-family: "Material Symbols Outlined", "Material Icons", "FontAwesome";
95
+ font-size: 24px;
96
+ width: 24px;
97
+ height: 24px;
98
+ text-align: center;
99
+ line-height: 24px;
74
100
  }
75
101
 
76
- .lm-tabs div[data-visible='false'] {
77
- display: none;
78
- }
102
+ .lm-tabs div[data-visible="false"] {
103
+ display: none;
104
+ }
package/package.json CHANGED
@@ -13,8 +13,8 @@
13
13
  "tabs js"
14
14
  ],
15
15
  "dependencies": {
16
- "lemonadejs": "^4.3.3"
16
+ "lemonadejs": "^5.2.0"
17
17
  },
18
18
  "main": "dist/index.js",
19
- "version": "2.3.0"
19
+ "version": "5.2.0"
20
20
  }