@lemonadejs/tabs 2.2.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -7,14 +7,18 @@ declare function Tabs(el: HTMLElement, options?: Tabs.Options): Tabs.Instance;
7
7
 
8
8
  declare namespace Tabs {
9
9
 
10
- interface Items {
10
+ interface Item {
11
+ // Reference to an external DOM
12
+ el: HTMLElement,
13
+ // Tab header
11
14
  title: string,
15
+ // HTML template
12
16
  content: string,
13
17
  }
14
18
 
15
19
  interface Options {
16
20
  /** Programmatically content */
17
- data?: Items[];
21
+ data?: Item[];
18
22
  /** Selected tab */
19
23
  selected?: number;
20
24
  /** Tabs position */
@@ -22,12 +26,16 @@ declare namespace Tabs {
22
26
  /** Activate round borders */
23
27
  round?: boolean;
24
28
  /** On open event */
25
- onopen?: (index: number) => void;
29
+ onopen?: (instance: object, index: number) => void;
30
+ /** Allow to create new tab button */
31
+ allowCreate?: boolean;
26
32
  }
27
33
 
28
34
  interface Instance {
35
+ /** Create a new option */
36
+ create: (item: Item, position?: number, select?: boolean) => void;
29
37
  /** Programmatically content */
30
- data?: Items[];
38
+ data: Item[];
31
39
  /** Selected tab */
32
40
  selected: number;
33
41
  /** Tabs position */
@@ -35,7 +43,7 @@ declare namespace Tabs {
35
43
  /** Activate round borders */
36
44
  round?: boolean;
37
45
  /** On open event */
38
- onopen?: (index: number) => void;
46
+ onopen?: (instance: object, index: number) => void;
39
47
  }
40
48
  }
41
49
 
package/dist/index.js CHANGED
@@ -17,76 +17,150 @@ if (! lemonade && typeof (require) === 'function') {
17
17
  }
18
18
  }
19
19
 
20
- const Tabs = function (html) {
20
+ const extract = function(root, self) {
21
+ if (! Array.isArray(self.data)) {
22
+ self.data = [];
23
+ }
24
+
25
+ if (root.tagName) {
26
+ for (let i = 0; i < root.children.length; i++) {
27
+ self.data.push({
28
+ el: root.children[i],
29
+ })
30
+ }
31
+ } else {
32
+ root.forEach((child) => {
33
+ self.data.push({
34
+ el: child.element,
35
+ })
36
+ });
37
+ }
38
+ }
39
+
40
+ const Tabs = function(children) {
21
41
  let self = this
22
- let elements = [];
23
42
 
43
+ // Add new tab
44
+ let createButton;
45
+
46
+ // Get the references from the root web component
47
+ let root;
48
+ let template = '';
24
49
  if (this.tagName) {
25
- // Remove elements from the DOM
26
- elements = removeElements(this);
50
+ root = this;
51
+ } else {
52
+ // References from LemonadeJS
53
+ if (typeof(children) === 'string') {
54
+ // Version 4
55
+ template = children;
56
+ } else if (children && children.length) {
57
+ // Version 5
58
+ root = children;
59
+ }
27
60
  }
28
61
 
62
+ if (root) {
63
+ extract(root, self);
64
+ }
65
+
66
+ // Process the data
29
67
  if (self.data) {
30
68
  for (let i = 0; i < self.data.length; i++) {
31
- let d = document.createElement('div');
32
- d.title = self.data[i].title;
33
- d.innerHTML = self.data[i].content;
34
- elements.push(d);
69
+ if (! self.data[i].el) {
70
+ // Create element
71
+ self.data[i].el = document.createElement('div');
72
+ // Create from content
73
+ if (self.data[i].content) {
74
+ self.data[i].el.innerHTML = self.data[i].content;
75
+ }
76
+ }
35
77
  }
36
78
  }
37
79
 
38
- if (! html) {
39
- html = '';
40
- }
41
-
42
- self.tabs = [];
43
-
44
80
  const select = function (index) {
81
+ // Make sure the index is a number
45
82
  index = parseInt(index);
46
-
47
83
  // Do not select tabs that does not exist
48
- if (index >= 0 && index < self.tabs.length) {
49
- for (let i = 0; i < self.content.children.length; i++) {
84
+ if (index >= 0 && index < self.data.length) {
85
+ for (let i = 0; i < self.root.children.length; i++) {
50
86
  self.headers.children[i].classList.remove('selected');
51
- self.content.children[i].classList.remove('selected');
87
+ self.root.children[i].classList.remove('selected');
52
88
  }
53
89
  self.headers.children[index].classList.add('selected');
54
- self.content.children[index].classList.add('selected');
90
+ self.root.children[index].classList.add('selected');
55
91
  }
56
92
  }
57
93
 
58
- self.onload = function () {
59
- // Append elements to the container
60
- appendElements(self.el.children[1], elements);
94
+ const init = function(selected) {
95
+ let tabs = [];
96
+ for (let i = 0; i < self.data.length; i++) {
97
+ // Extract meta information from the DOM
98
+ if (! self.data[i].title) {
99
+ let ret = self.data[i].el.getAttribute('title');
100
+ if (ret != null) {
101
+ self.data[i].title = ret;
102
+ }
103
+ }
104
+ if (! self.data[i].selected) {
105
+ let ret = self.data[i].el.getAttribute('selected');
106
+ if (ret != null) {
107
+ self.data[i].selected = ret;
108
+ }
109
+ }
110
+ // Create tabs object
111
+ tabs[i] = {
112
+ title: self.data[i].title,
113
+ }
114
+ // Which one is selected by default
115
+ if (self.data[i].selected) {
116
+ selected = i;
117
+ }
61
118
 
62
- for (let i = 0; i < self.content.children.length; i++) {
63
- self.tabs.push({ title: self.content.children[i].title });
119
+ self.root.appendChild(self.data[i].el)
64
120
  }
65
- self.refresh('tabs');
66
-
67
- let index = 0;
68
- if (! isNaN(parseInt(self.selected))) {
69
- index = parseInt(self.selected);
121
+ // Create headers
122
+ self.tabs = tabs;
123
+ // Default selected
124
+ if (typeof(selected) !== 'undefined') {
125
+ self.selected = selected;
126
+ }
127
+ // Add create new tab button
128
+ if (createButton) {
129
+ self.headers.appendChild(createButton);
70
130
  }
71
- select(index);
72
131
  }
73
132
 
74
-
75
- self.onchange = function (property) {
76
- if (property === 'selected') {
77
- select(self.selected);
78
-
79
- Dispatch.call(self, 'onopen', self, self.selected);
133
+ self.onload = function () {
134
+ if (template) {
135
+ extract(self.root, self);
80
136
  }
137
+
138
+ init(self.selected || 0);
81
139
  }
82
140
 
83
- self.keydown = function(e) {
141
+ self.keydown = function(e, s) {
142
+ let index = null;
84
143
  if (e.key === 'Enter') {
85
- self.click(e);
144
+ self.click(e, s);
145
+ } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
146
+ index = self.selected - 1;
147
+ if (index < 0) {
148
+ index = 0;
149
+ }
150
+ } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
151
+ index = self.selected + 1;
152
+ if (index > self.tabs.length-1) {
153
+ index = self.tabs.length-1;
154
+ }
155
+ }
156
+
157
+ // Make selection
158
+ if (index !== null) {
159
+ self.tabs[index].el.focus();
86
160
  }
87
161
  }
88
162
 
89
- self.click = function (e) {
163
+ self.open = function (e) {
90
164
  if (e.target.tagName === 'LI') {
91
165
  // Avoid select something already selected
92
166
  let index = Array.prototype.indexOf.call(e.target.parentNode.children, e.target);
@@ -96,50 +170,78 @@ if (! lemonade && typeof (require) === 'function') {
96
170
  }
97
171
  }
98
172
 
99
- return `<div class="lm-tabs" data-position="{{self.position}}" data-round="{{self.round}}">
100
- <ul :ref="self.headers" :loop="self.tabs" :selected="self.selected" onclick="self.click" onkeydown="self.keydown" role="tabs"><li class="lm-tab-list-item" tabindex="0" role="tab">{{self.title}}</li></ul>
101
- <div :ref="self.content" class="lm-tabs-content">${html}</div>
102
- </div>`
103
- }
173
+ self.create = function(item, position, select) {
174
+ // Create element
175
+ if (typeof(item) !== 'object') {
176
+ console.error('Item must be an object');
177
+ } else {
178
+ // Create DOM
179
+ item.el = document.createElement('div');
180
+ // Create from content
181
+ if (item.content) {
182
+ item.el.innerHTML = item.content;
183
+ }
104
184
 
105
- const removeElements = function(root) {
106
- // Keep the DOM elements
107
- let elements = [];
108
- if (root) {
109
- while (root.firstChild) {
110
- elements.push(root.firstChild);
111
- root.firstChild.remove();
185
+ // Add the new item in the end
186
+ if (typeof(position) === 'undefined' || position === null) {
187
+ // Mew item
188
+ position = self.data.length;
189
+ // Add in the end
190
+ self.data.push(item);
191
+ } else {
192
+ self.data.splice(position, 0, item);
193
+ }
194
+ // New position
195
+ if (select) {
196
+ // Refresh
197
+ init(self.data.indexOf(item));
198
+ } else {
199
+ init(self.selected);
200
+ }
112
201
  }
113
202
  }
114
- return elements;
115
- }
116
203
 
117
- const appendElements = function(root, elements) {
118
- if (elements && elements.length) {
119
- while (elements[0]) {
120
- root.appendChild(elements.shift());
121
- }
204
+ self.click = function() {
205
+ // Create a new item
206
+ self.create({ title: 'Untitled' }, null, true);
122
207
  }
123
- }
124
208
 
125
- const Component = function (root, options) {
126
- if (typeof (root) === 'object') {
127
- // Remove elements from the DOM
128
- let elements = removeElements(root);
129
- // Create the modal
130
- let e = lemonade.render(Tabs, root, options);
131
- // Add elements to the container
132
- appendElements(e.children[1], elements);
209
+ self.onchange = function (property) {
210
+ if (property === 'selected') {
211
+ select(self.selected);
133
212
 
134
- return options;
135
- } else {
136
- return Tabs.call(this);
213
+ Dispatch.call(self, 'onopen', self, self.selected);
214
+ }
137
215
  }
216
+
217
+ self.allowCreate = !! self.allowCreate;
218
+
219
+ return `<div class="lm-tabs" data-position="{{self.position}}" data-round="{{self.round}}">
220
+ <div role="tabs" class="lm-tabs-headers">
221
+ <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>
222
+ <div data-visible="{{self.allowCreate}}" class="lm-tabs-insert-tab material-icons" role="insert-tab" onclick="self.click">add</div>
223
+ </div>
224
+ <div :ref="self.root" class="lm-tabs-content">${template}</div>
225
+ </div>`
138
226
  }
139
227
 
140
228
  lemonade.setComponents({ Tabs: Tabs });
141
229
 
142
230
  lemonade.createWebComponent('tabs', Tabs);
143
231
 
144
- return Component;
232
+ return function (root, options) {
233
+ if (typeof (root) === 'object') {
234
+ if (typeof(options) !== 'object') {
235
+ options = {};
236
+ }
237
+ // Extract DOM references
238
+ extract(root, options);
239
+ // Create the modal
240
+ lemonade.render(Tabs, root, options);
241
+ // Return self
242
+ return options;
243
+ } else {
244
+ return Tabs.call(this);
245
+ }
246
+ };
145
247
  })));
package/dist/style.css CHANGED
@@ -2,47 +2,58 @@
2
2
  padding: 15px 5px;
3
3
  }
4
4
 
5
- .lm-tabs > ul {
5
+ .lm-tabs .lm-tabs-headers {
6
+ display: flex;
7
+ align-items: center;
8
+ width: 100%;
9
+ }
10
+
11
+ .lm-tabs[data-position="center"] .lm-tabs-headers {
12
+ margin: 0 auto;
13
+ justify-content: center;
14
+ }
15
+
16
+ .lm-tabs .lm-tabs-headers > ul {
6
17
  list-style-type: none;
7
18
  display: flex;
8
19
  margin: 0;
9
20
  padding: 0;
10
- width: 100%;
21
+ align-items: center;
11
22
  }
12
23
 
13
- .lm-tabs > ul > li {
24
+ .lm-tabs .lm-tabs-headers > ul > li {
14
25
  cursor: pointer;
15
26
  user-select: none;
16
- padding: 6px 24px;
27
+ padding: 4px 24px;
17
28
  border: 1px solid #ccc;
18
29
  background-position: center;
19
30
  transition: background 0.8s;
20
31
  }
21
32
 
22
- .lm-tabs > ul > li.selected {
33
+ .lm-tabs .lm-tabs-headers > ul > li.selected {
23
34
  background-color: #eee;
24
35
  color: #000;
25
36
  }
26
37
 
27
- .lm-tabs[data-round="true"] > ul > li:first-child {
38
+ .lm-tabs[data-round="true"] .lm-tabs-headers > ul > li:first-of-type {
28
39
  border-top-left-radius: 3px;
29
40
  border-bottom-left-radius: 3px;
30
41
  }
31
42
 
32
- .lm-tabs[data-round="true"] > ul > li:last-child {
43
+ .lm-tabs[data-round="true"] .lm-tabs-headers > ul > li:last-of-type {
33
44
  border-top-right-radius: 3px;
34
45
  border-bottom-right-radius: 3px;
35
46
  }
36
47
 
37
- .lm-tabs > ul > li:not(:first-child) {
38
- border-left: 0;
48
+ .lm-tabs .lm-tabs-headers > ul > li:not(:first-child) {
49
+ border-left: 1px solid transparent;
39
50
  }
40
51
 
41
- .lm-tabs > ul > li:hover {
52
+ .lm-tabs .lm-tabs-headers > ul > li:hover {
42
53
  background: #eee radial-gradient(circle, transparent 1%, #eee 1%) center/15000%;
43
54
  }
44
55
 
45
- .lm-tabs > ul > li:active {
56
+ .lm-tabs .lm-tabs-headers > ul > li:active {
46
57
  background-color: #ddd;
47
58
  background-size: 100%;
48
59
  transition: background 0s;
@@ -56,7 +67,12 @@
56
67
  display: block;
57
68
  }
58
69
 
59
- .lm-tabs[data-position="center"] > ul {
60
- margin: 0 auto;
61
- justify-content: center;
70
+ .lm-tabs-insert-tab {
71
+ margin-left: 5px;
72
+ color: #555;
73
+ cursor: pointer;
74
+ }
75
+
76
+ .lm-tabs div[data-visible='false'] {
77
+ display: none;
62
78
  }
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.0.3"
17
17
  },
18
18
  "main": "dist/index.js",
19
- "version": "2.2.0"
19
+ "version": "5.0.0"
20
20
  }