Skip to content

Commit 3a9830d

Browse files
megothArne Hassel
authored andcommitted
Written tests for tabs
1 parent 645965a commit 3a9830d

4 files changed

Lines changed: 383 additions & 155 deletions

File tree

src/tabs.ts

Lines changed: 145 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -10,66 +10,51 @@
1010
import store from './store'
1111
import { cancelButton } from './widgets'
1212
import { label } from './utils'
13+
import { NamedNode } from 'rdflib'
1314

14-
// options.subject
15-
// options.orientation 0 top, 1 left, 2 bottom, 3 right
16-
// options.showMain(div, subject) function to show subject in div when tab selected
17-
// options.renderTabSettings function(subject, domContainer)
18-
// options.renderTabSettings like showMain but when user has held Alt down
19-
// options.onClose if given, will present a cancelButton next to tabs that calls this optional method
20-
//
15+
type TabWidgetOptions = {
16+
backgroundColor?: string
17+
dom: HTMLDocument
18+
items?: Array<NamedNode>
19+
onClose?: (event: Event) => void
20+
ordered?: boolean
21+
orientation?: '0' | '1' | '2' | '3'
22+
predicate?: NamedNode
23+
renderMain?: (bodyMain: HTMLElement, subject: NamedNode) => void
24+
renderTab?: (tabDiv: HTMLDivElement, subject: NamedNode) => void
25+
renderTabSettings?: (bodyMain: ContainerElement, subject: NamedNode) => void
26+
selectedTab?: string
27+
startEmpty?: boolean
28+
subject: NamedNode
29+
}
30+
31+
export class TabWidgetElement extends HTMLElement {
32+
bodyContainer?: HTMLElement
33+
refresh?: () => void
34+
tabContainer?: HTMLElement
35+
}
36+
37+
class ContainerElement extends HTMLElement {
38+
asSettings?: boolean
39+
}
2140

22-
export function tabWidget (options) {
23-
const kb = store
41+
class TabElement extends HTMLElement {
42+
bodyTR?: HTMLElement
43+
subject?: NamedNode
44+
}
45+
46+
export function tabWidget (options: TabWidgetOptions) {
2447
const subject = options.subject
2548
const dom = options.dom
2649
const orientation = parseInt(options.orientation || '0')
2750
const backgroundColor = options.backgroundColor || '#ddddcc'
28-
let color
2951
const flipped = orientation & 2
3052
const vertical = orientation & 1
3153
const onClose = options.onClose
3254

33-
const isLight = function (x) {
34-
let total = 0
35-
for (let i = 0; i < 3; i++) {
36-
total += parseInt(x.slice(i * 2 + 1, i * 2 + 3), 16)
37-
}
38-
return total > 128 * 3
39-
}
40-
const colorBlend = function (a, b, mix) {
41-
let ca, cb, res
42-
let str = '#'
43-
const hex = '0123456789abcdef'
44-
for (let i = 0; i < 3; i++) {
45-
ca = parseInt(a.slice(i * 2 + 1, i * 2 + 3), 16)
46-
cb = parseInt(b.slice(i * 2 + 1, i * 2 + 3), 16)
47-
res = ca * (1.0 - mix) + cb * mix // @@@ rounding
48-
const res2 = parseInt(('' + res).split('.')[0]) // @@ ugh
49-
const h = parseInt(('' + res2 / 16).split('.')[0]) // @@ ugh
50-
const l = parseInt(('' + (res2 % 16)).split('.')[0]) // @@ ugh
51-
str += hex[h] + hex[l]
52-
}
53-
// console.log('Blending colors ' + a + ' with ' + mix + ' of ' + b + ' to give ' + str)
54-
return str
55-
}
56-
57-
let selectedColor
58-
if (isLight(backgroundColor)) {
59-
selectedColor = colorBlend(backgroundColor, '#ffffff', 0.3)
60-
color = '#000000'
61-
} else {
62-
selectedColor = colorBlend(backgroundColor, '#000000', 0.3)
63-
color = '#ffffff'
64-
}
55+
const [selectedColor, color] = getColors(backgroundColor)
6556
const bodyMainStyle = `flex: 2; width: auto; height: 100%; border: 0.1em; border-style: solid; border-color: ${selectedColor}; padding: 1em;`
66-
67-
/*
68-
'resize: both; overflow: scroll; margin:0; border: 0.1em; border-style: solid; border-color: ' +
69-
selectedColor +
70-
'; padding: 1em; min-width: 30em; min-height: 450px; width:100%;'
71-
*/
72-
const rootElement = dom.createElement('div') // 20200117a
57+
const rootElement: TabWidgetElement = dom.createElement('div') // 20200117a
7358

7459
rootElement.setAttribute('style', 'display: flex; height: 100%; width: 100%; flex-direction: ' +
7560
(vertical ? 'row' : 'column') + (flipped ? '-reverse;' : ';'))
@@ -81,64 +66,76 @@ export function tabWidget (options) {
8166

8267
mainElement.setAttribute('style', 'margin: 0; width:100%; height: 100%;') // override tabbedtab.css
8368
const tabContainer = navElement.appendChild(dom.createElement('ul'))
84-
tabContainer.setAttribute('style', 'list-style-type: none;' + // No bullet please ...
85-
'display: flex; height: 100%; width: 100%; flex-direction: ' +
86-
(vertical ? 'column' : 'row')) // + (flipped ? '-reverse;' : ';')
87-
// Never flip the direction of readuing the tabs, assuming people read left to right and top to bottom
69+
tabContainer.setAttribute('style', `
70+
list-style-type: none;
71+
display: flex;
72+
height: 100%;
73+
width: 100%;
74+
padding: 0;
75+
flex-direction: ${(vertical ? 'column' : 'row')}
76+
`)
8877

8978
const tabElement = 'li'
9079

9180
const bodyContainer = mainElement // .appendChild(dom.createElement('table'))
9281
rootElement.tabContainer = tabContainer // ussed by caller
9382
rootElement.bodyContainer = bodyContainer
9483

95-
const getItems = function () {
96-
if (options.items) return options.items
97-
if (options.ordered !== false) {
98-
// default to true
99-
const list = kb.the(subject, options.predicate)
100-
return list.elements
101-
} else {
102-
return kb.each(subject, options.predicate)
103-
}
104-
}
105-
106-
let corners = ['2em', '2em', '0', '0'] // top left, TR, BR, BL
107-
corners = corners.concat(corners).slice(orientation, orientation + 4)
108-
const cornersStyle = 'border-radius: ' + corners.join(' ') + ';'
84+
const corners = ['2em', '2em', '0', '0'] // top left, TR, BR, BL
85+
const cornersPrepped = corners.concat(corners).slice(orientation, orientation + 4)
86+
const cornersStyle = 'border-radius: ' + cornersPrepped.join(' ') + ';'
10987

110-
let margins = ['0.3em', '0.3em', '0', '0.3em'] // top, right, bottom, left
111-
margins = margins.concat(margins).slice(orientation, orientation + 4)
112-
const marginsStyle = 'margin: ' + margins.join(' ') + ';'
88+
const margins = ['0.3em', '0.3em', '0', '0.3em'] // top, right, bottom, left
89+
const marginsPrepped = margins.concat(margins).slice(orientation, orientation + 4)
90+
const marginsStyle = 'margin: ' + marginsPrepped.join(' ') + ';'
11391

114-
let tabStyle = cornersStyle + 'padding: 0.7em; max-width: 20em;' // border: 0.05em 0 0.5em 0.05em; border-color: grey;
115-
tabStyle += 'color: ' + color + ';'
116-
const unselectedStyle =
117-
tabStyle +
118-
'opacity: 50%; margin: 0.3em; background-color: ' +
119-
backgroundColor +
120-
';' // @@ rotate border
92+
const tabStyle = cornersStyle + `padding: 0.7em; max-width: 20em;color: ${color};` // border: 0.05em 0 0.5em 0.05em; border-color: grey;
93+
const unselectedStyle = `${tabStyle}opacity: 50%; margin: 0.3em; background-color: ${backgroundColor};` // @@ rotate border
12194
const selectedStyle = tabStyle + marginsStyle + ' background-color: ' + selectedColor + ';'
12295
const shownStyle = 'height: 100%; width: 100%;'
12396
const hiddenStyle = shownStyle + 'display: none;'
97+
rootElement.refresh = sync
98+
sync()
12499

125-
const resetTabStyle = function () {
126-
for (let i = 0; i < tabContainer.children.length; i++) {
127-
const tab = tabContainer.children[i]
128-
if (tab.classList.contains('unstyled')) {
129-
continue
130-
}
131-
tab.firstChild.setAttribute('style', unselectedStyle)
100+
// From select-tabs branch by hand
101+
if (!options.startEmpty && tabContainer.children.length && options.selectedTab) {
102+
const selectedTab = Array.from(tabContainer.children)
103+
.map(tab => tab.firstChild as HTMLElement)
104+
.find(tab => tab.dataset.name === options.selectedTab)
105+
const tab = selectedTab || tabContainer.children[0].firstChild as HTMLButtonElement
106+
tab.click()
107+
} else if (!options.startEmpty) {
108+
(tabContainer.children[0].firstChild as HTMLButtonElement).click() // Open first tab
109+
}
110+
return rootElement
111+
112+
function addCancelButton (tabContainer) {
113+
if (tabContainer.dataset.onCloseSet) {
114+
// @@ TODO: this is only here to make the tests work
115+
// Discussion at https://github.com/solid/solid-ui/pull/110#issuecomment-527080663
116+
const existingCancelButton = tabContainer.querySelector('.unstyled')
117+
tabContainer.removeChild(existingCancelButton)
132118
}
119+
const extraTab = dom.createElement(tabElement)
120+
extraTab.classList.add('unstyled')
121+
const tabCancelButton = cancelButton(dom, onClose)
122+
extraTab.appendChild(tabCancelButton)
123+
tabContainer.appendChild(extraTab)
124+
tabContainer.dataset.onCloseSet = 'true'
133125
}
134-
const resetBodyStyle = function () {
135-
for (let i = 0; i < bodyContainer.children.length; i++) {
136-
bodyContainer.children[i].setAttribute('style', hiddenStyle)
126+
127+
function getItems (): Array<NamedNode> {
128+
if (options.items) return options.items
129+
if (options.ordered !== false) {
130+
// default to true
131+
return store.the(subject, options.predicate).elements
132+
} else {
133+
return store.each(subject, options.predicate)
137134
}
138135
}
139136

140-
const makeNewSlot = function (item) {
141-
const ele = dom.createElement(tabElement)
137+
function makeNewSlot (item: NamedNode) {
138+
const ele: TabElement = dom.createElement(tabElement)
142139
ele.subject = item
143140
const div = ele.appendChild(dom.createElement('div'))
144141
div.setAttribute('style', unselectedStyle)
@@ -149,23 +146,24 @@ export function tabWidget (options) {
149146
resetBodyStyle()
150147
}
151148
div.setAttribute('style', selectedStyle)
149+
if (!ele.bodyTR) return
152150
ele.bodyTR.setAttribute('style', shownStyle)
153-
let bodyMain = ele.bodyTR.firstChild
151+
let bodyMain = ele.bodyTR.children[0] as ContainerElement
154152
if (!bodyMain) {
155153
bodyMain = ele.bodyTR.appendChild(dom.createElement('main'))
156154
bodyMain.setAttribute('style', bodyMainStyle)
157155
}
158-
if (options.renderTabSettings && e.altKey) {
159-
if (bodyMain.asSetttings !== true) {
156+
if (options.renderTabSettings && e.altKey && ele.subject) {
157+
if (bodyMain.asSettings !== true) {
160158
bodyMain.innerHTML = 'loading settings ...' + item
161159
options.renderTabSettings(bodyMain, ele.subject)
162-
bodyMain.asSetttings = true
160+
bodyMain.asSettings = true
163161
}
164-
} else {
165-
if (bodyMain.asSetttings !== false) {
162+
} else if (options.renderMain && ele.subject) {
163+
if (bodyMain.asSettings !== false) {
166164
bodyMain.innerHTML = 'loading item ...' + item
167165
options.renderMain(bodyMain, ele.subject)
168-
bodyMain.asSetttings = false
166+
bodyMain.asSettings = false
169167
}
170168
}
171169
})
@@ -179,16 +177,16 @@ export function tabWidget (options) {
179177
}
180178

181179
// @@ Use common one from utils?
182-
const orderedSync = function () {
180+
function orderedSync () {
183181
const items = getItems()
184182
if (!vertical) {
185183
// mainElement.setAttribute('colspan', items.length + (onClose ? 1 : 0))
186184
}
187-
let slot, i, j, left, right
185+
let slot: TabElement, i, j, left, right
188186
let differ = false
189187
// Find how many match at each end
190188
for (left = 0; left < tabContainer.children.length; left++) {
191-
slot = tabContainer.children[left]
189+
slot = tabContainer.children[left] as TabElement
192190
if (
193191
left >= items.length ||
194192
(slot.subject && !slot.subject.sameTerm(items[left]))
@@ -201,17 +199,14 @@ export function tabWidget (options) {
201199
return // The two just match in order: a case to optimize for
202200
}
203201
for (right = tabContainer.children.length - 1; right >= 0; right--) {
204-
slot = tabContainer.children[right]
202+
slot = tabContainer.children[right] as TabElement
205203
j = right - tabContainer.children.length + items.length
206204
if (slot.subject && !slot.subject.sameTerm(items[j])) {
207205
break
208206
}
209207
}
210208
// The elements left ... right in tabContainer.children do not match
211-
const insertables = items.slice(
212-
left,
213-
right - tabContainer.children.length + items.length + 1
214-
)
209+
const insertables = items.slice(left, right - tabContainer.children.length + items.length + 1)
215210
while (right >= left) {
216211
// remove extra
217212
tabContainer.removeChild(tabContainer.children[left])
@@ -239,55 +234,61 @@ export function tabWidget (options) {
239234
}
240235
}
241236

242-
const sync = function () {
237+
function resetTabStyle () {
238+
for (let i = 0; i < tabContainer.children.length; i++) {
239+
const tab = tabContainer.children[i]
240+
if (tab.classList.contains('unstyled')) {
241+
continue
242+
}
243+
if (tab.children[0]) {
244+
tab.children[0].setAttribute('style', unselectedStyle)
245+
}
246+
}
247+
}
248+
249+
function resetBodyStyle () {
250+
for (let i = 0; i < bodyContainer.children.length; i++) {
251+
bodyContainer.children[i].setAttribute('style', hiddenStyle)
252+
}
253+
}
254+
255+
function sync () {
243256
if (options.ordered) {
244257
orderedSync()
245258
} else {
246259
// @@ SORT THE values
247260
orderedSync()
248261
}
249262
}
250-
rootElement.refresh = sync
251-
sync()
263+
}
252264

253-
// From select-tabs branch by hand
254-
if (
255-
!options.startEmpty &&
256-
tabContainer.children.length &&
257-
options.selectedTab
258-
) {
259-
let tab
260-
let found = false
261-
for (let i = 0; i < tabContainer.children.length; i++) {
262-
tab = tabContainer.children[i]
263-
if (
264-
tab.firstChild &&
265-
tab.firstChild.dataset.name === options.selectedTab
266-
) {
267-
tab.firstChild.click()
268-
found = true
269-
}
270-
}
271-
if (!found) {
272-
tabContainer.children[0].firstChild.click() // Open first tab
273-
}
274-
} else if (!options.startEmpty && tabContainer.children.length) {
275-
tabContainer.children[0].firstChild.click() // Open first tab
265+
function getColors (backgroundColor: string): [string, string] {
266+
return isLight(backgroundColor)
267+
? [colorBlend(backgroundColor, '#ffffff', 0.3), '#000000']
268+
: [colorBlend(backgroundColor, '#000000', 0.3), '#ffffff']
269+
}
270+
271+
function colorBlend (a: string, b: string, mix: number): string {
272+
let ca, cb, res
273+
let str = '#'
274+
const hex = '0123456789abcdef'
275+
for (let i = 0; i < 3; i++) {
276+
ca = parseInt(a.slice(i * 2 + 1, i * 2 + 3), 16)
277+
cb = parseInt(b.slice(i * 2 + 1, i * 2 + 3), 16)
278+
res = ca * (1.0 - mix) + cb * mix // @@@ rounding
279+
const res2 = parseInt(('' + res).split('.')[0]) // @@ ugh
280+
const h = parseInt(('' + res2 / 16).split('.')[0]) // @@ ugh
281+
const l = parseInt(('' + (res2 % 16)).split('.')[0]) // @@ ugh
282+
str += hex[h] + hex[l]
276283
}
277-
return rootElement
284+
// console.log('Blending colors ' + a + ' with ' + mix + ' of ' + b + ' to give ' + str)
285+
return str
286+
}
278287

279-
function addCancelButton (tabContainer) {
280-
if (tabContainer.dataset.onCloseSet) {
281-
// @@ TODO: this is only here to make the tests work
282-
// Discussion at https://github.com/solid/solid-ui/pull/110#issuecomment-527080663
283-
const existingCancelButton = tabContainer.querySelector('.unstyled')
284-
tabContainer.removeChild(existingCancelButton)
285-
}
286-
const extraTab = dom.createElement(tabElement)
287-
extraTab.classList.add('unstyled')
288-
const tabCancelButton = cancelButton(dom, onClose)
289-
extraTab.appendChild(tabCancelButton)
290-
tabContainer.appendChild(extraTab)
291-
tabContainer.dataset.onCloseSet = 'true'
288+
function isLight (x: string): boolean {
289+
let total = 0
290+
for (let i = 0; i < 3; i++) {
291+
total += parseInt(x.slice(i * 2 + 1, i * 2 + 3), 16)
292292
}
293+
return total > 128 * 3
293294
}

0 commit comments

Comments
 (0)