@qtoggle/qui 1.20.0 → 1.20.2
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/js/icons/multi-state-sprites-icon.js +59 -41
- package/js/utils/array.js +42 -6
- package/js/views/common-views/icon-label-view.js +22 -11
- package/js/widgets/common-widgets/progress-disk.js +29 -17
- package/less/base.less +4 -0
- package/less/icon-label-view.less +7 -40
- package/less/icons.less +4 -0
- package/less/lists.less +48 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/qui/templates/service-worker.js +5 -5
|
@@ -138,31 +138,40 @@ class MultiStateSpritesIcon extends Icon {
|
|
|
138
138
|
let bgWidth = params.bgWidth
|
|
139
139
|
let bgHeight = params.bgHeight
|
|
140
140
|
|
|
141
|
+
let iconElement = element[0]
|
|
142
|
+
|
|
141
143
|
/* Find and remove existing icon state elements */
|
|
142
|
-
let existingStateElements =
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
let existingStateElements = []
|
|
145
|
+
for (let i = 0; i < iconElement.children.length; i++) {
|
|
146
|
+
let child = iconElement.children[i]
|
|
147
|
+
let isState = child.className.split(' ').some(function (c) {
|
|
148
|
+
return c !== 'qui-icon-decoration' && c.startsWith('qui-icon-') && c.length > 'qui-icon-'.length
|
|
145
149
|
})
|
|
146
|
-
|
|
150
|
+
if (isState) {
|
|
151
|
+
existingStateElements.push(child)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
if (iconElement.isConnected) {
|
|
156
|
+
/* Only this branch fades the old elements out, and only it needs them as a jQuery object */
|
|
157
|
+
let oldElements = $(existingStateElements)
|
|
150
158
|
asap(function () {
|
|
151
|
-
|
|
159
|
+
oldElements.addClass('qui-icon-hidden')
|
|
152
160
|
})
|
|
153
161
|
Theme.afterTransition(function () {
|
|
154
|
-
|
|
155
|
-
},
|
|
162
|
+
oldElements.remove()
|
|
163
|
+
}, oldElements)
|
|
156
164
|
}
|
|
157
165
|
else {
|
|
158
|
-
existingStateElements.remove()
|
|
166
|
+
existingStateElements.forEach(e => e.remove())
|
|
159
167
|
existingStateElements = []
|
|
160
168
|
}
|
|
161
169
|
|
|
162
|
-
|
|
170
|
+
iconElement.classList.add('qui-icon')
|
|
163
171
|
|
|
164
|
-
/* Add new icon state elements
|
|
165
|
-
|
|
172
|
+
/* Add new icon state elements. Built with plain DOM calls because this runs for every icon on screen, and
|
|
173
|
+
* jQuery's element, style and append helpers were about half of what renderTo cost on a 448-row list. */
|
|
174
|
+
let newElements = []
|
|
166
175
|
ObjectUtils.forEach(this._states, function (state, details) {
|
|
167
176
|
|
|
168
177
|
let offsetX = details.offsetX
|
|
@@ -173,35 +182,33 @@ class MultiStateSpritesIcon extends Icon {
|
|
|
173
182
|
return
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
let stateDiv =
|
|
185
|
+
let stateDiv = document.createElement('div')
|
|
186
|
+
stateDiv.className = `qui-icon-${state}`
|
|
177
187
|
/* If no existing elements, display the new element directly, w/o any effect; otherwise start hidden and
|
|
178
188
|
* do a transition to visible */
|
|
179
189
|
if (existingStateElements.length) {
|
|
180
|
-
stateDiv.
|
|
190
|
+
stateDiv.classList.add('qui-icon-hidden')
|
|
181
191
|
}
|
|
182
192
|
|
|
183
|
-
let
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
193
|
+
let style = stateDiv.style
|
|
194
|
+
style.backgroundImage = `url("${this._url}")`
|
|
195
|
+
style.backgroundPosition = `${(-offsetX * size)}${this._unit} ${(-offsetY * size)}${this._unit}`
|
|
187
196
|
if (bgWidth && bgHeight) {
|
|
188
|
-
|
|
197
|
+
style.backgroundSize = `${bgWidth} ${bgHeight}`
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
if (details.filter) {
|
|
192
|
-
|
|
201
|
+
style.filter = details.filter
|
|
193
202
|
}
|
|
194
203
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
element.append(stateDiv)
|
|
198
|
-
newElements = newElements.add(stateDiv)
|
|
204
|
+
iconElement.appendChild(stateDiv)
|
|
205
|
+
newElements.push(stateDiv)
|
|
199
206
|
|
|
200
207
|
}, this)
|
|
201
208
|
|
|
202
209
|
if (existingStateElements.length) {
|
|
203
210
|
asap(function () {
|
|
204
|
-
newElements.
|
|
211
|
+
newElements.forEach(e => e.classList.remove('qui-icon-hidden'))
|
|
205
212
|
})
|
|
206
213
|
}
|
|
207
214
|
|
|
@@ -212,19 +219,23 @@ class MultiStateSpritesIcon extends Icon {
|
|
|
212
219
|
}
|
|
213
220
|
}, this)
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
|
|
222
|
+
iconElement.classList.remove(...this.constructor.KNOWN_STATES.map(s => `top-${s}`))
|
|
223
|
+
iconElement.classList.add(`top-${topState}`)
|
|
217
224
|
|
|
218
225
|
/* Decoration */
|
|
219
226
|
|
|
220
|
-
let decorationDiv =
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
let decorationDiv = null
|
|
228
|
+
for (let i = 0; i < iconElement.children.length; i++) {
|
|
229
|
+
if (iconElement.children[i].classList.contains('qui-icon-decoration')) {
|
|
230
|
+
decorationDiv = iconElement.children[i]
|
|
231
|
+
break
|
|
232
|
+
}
|
|
223
233
|
}
|
|
224
234
|
|
|
225
235
|
if (!decorationDiv && this._decoration) {
|
|
226
|
-
decorationDiv =
|
|
227
|
-
|
|
236
|
+
decorationDiv = document.createElement('div')
|
|
237
|
+
decorationDiv.className = 'qui-icon-decoration'
|
|
238
|
+
iconElement.insertBefore(decorationDiv, iconElement.firstChild)
|
|
228
239
|
}
|
|
229
240
|
else if (decorationDiv && !this._decoration) {
|
|
230
241
|
decorationDiv.remove()
|
|
@@ -232,35 +243,42 @@ class MultiStateSpritesIcon extends Icon {
|
|
|
232
243
|
}
|
|
233
244
|
|
|
234
245
|
if (decorationDiv) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
246
|
+
decorationDiv.style.background = this._decoration
|
|
247
|
+
|
|
248
|
+
/* The decoration element outlives a render, so a colour lookup can resolve after another icon has been
|
|
249
|
+
* applied to the same element. Each render stamps the element, and only the latest one may write. */
|
|
250
|
+
let renderToken = (decorationDiv._quiRenderToken = (decorationDiv._quiRenderToken || 0) + 1)
|
|
238
251
|
|
|
239
252
|
let bgColor = this._findBgColor(element)
|
|
240
253
|
let bgRGB = Colors.str2rgba(bgColor)
|
|
241
254
|
let decoRGB = Colors.str2rgba(this._decoration)
|
|
242
255
|
if (Colors.contrast(bgRGB, decoRGB) > 1.5) {
|
|
243
|
-
|
|
256
|
+
decorationDiv.style.borderColor = bgColor
|
|
244
257
|
}
|
|
245
258
|
else {
|
|
246
259
|
this._findIconColor(element).then(function (color) {
|
|
247
|
-
|
|
260
|
+
if (decorationDiv._quiRenderToken === renderToken) {
|
|
261
|
+
decorationDiv.style.borderColor = color
|
|
262
|
+
}
|
|
248
263
|
})
|
|
249
264
|
}
|
|
250
|
-
|
|
251
|
-
decorationDiv.css(css)
|
|
252
265
|
}
|
|
253
266
|
}
|
|
254
267
|
|
|
255
268
|
_findBgColor(elem) {
|
|
256
269
|
/* Find the background color behind the icon */
|
|
257
270
|
|
|
271
|
+
/* Icons are usually rendered before their row is attached, and a detached element has no computed style, so
|
|
272
|
+
* up such a chain css() only ever reports the inline one. Reading that directly skips a getComputedStyle call
|
|
273
|
+
* per element, worth about 130 ms across a 448-row list. */
|
|
274
|
+
let attached = elem.length > 0 && elem[0].isConnected
|
|
275
|
+
|
|
258
276
|
let e = elem
|
|
259
277
|
let bgColor = null
|
|
260
278
|
while (e.length && e[0].tagName && (!bgColor || bgColor === 'rgba(0, 0, 0, 0)' ||
|
|
261
279
|
bgColor === 'transparent')) {
|
|
262
280
|
|
|
263
|
-
bgColor = e.css('background-color')
|
|
281
|
+
bgColor = attached ? e.css('background-color') : e[0].style.backgroundColor
|
|
264
282
|
e = e.parent()
|
|
265
283
|
}
|
|
266
284
|
|
package/js/utils/array.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* @namespace qui.utils.array
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
return function (
|
|
7
|
-
let k1 =
|
|
8
|
-
let k2 =
|
|
5
|
+
function makeKeySortFunc(desc) {
|
|
6
|
+
return function (p1, p2) {
|
|
7
|
+
let k1 = p1.key
|
|
8
|
+
let k2 = p2.key
|
|
9
9
|
|
|
10
10
|
if ((desc && k1 > k2) || (!desc && k1 < k2)) {
|
|
11
11
|
return -1
|
|
@@ -19,6 +19,36 @@ function makeSortFunc(extractFunc, desc, thisArg) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/* The sort runs over element-key pairs, so the key function runs once per element instead of twice for every
|
|
23
|
+
* comparison the sort makes. Sorting 448 items in random order took 6408 key extractions; this brings it to 448. */
|
|
24
|
+
function sortPairs(array, func, desc, thisArg, sortFunc) {
|
|
25
|
+
/* Each element carries its own key instead of the two being matched up in a Map, which would file 0 and -0 under
|
|
26
|
+
* one entry. Undefined entries and holes are held back and restored at the end, as Array#sort leaves them. */
|
|
27
|
+
let length = array.length
|
|
28
|
+
let pairs = []
|
|
29
|
+
let undefinedCount = 0
|
|
30
|
+
|
|
31
|
+
array.forEach(function (e) {
|
|
32
|
+
if (e === undefined) {
|
|
33
|
+
undefinedCount++
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
pairs.push({element: e, key: func.call(thisArg, e)})
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
sortFunc(pairs, makeKeySortFunc(desc))
|
|
41
|
+
|
|
42
|
+
array.length = 0
|
|
43
|
+
pairs.forEach(p => array.push(p.element))
|
|
44
|
+
while (undefinedCount--) {
|
|
45
|
+
array.push(undefined)
|
|
46
|
+
}
|
|
47
|
+
array.length = length
|
|
48
|
+
|
|
49
|
+
return array
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
function merge(left, right, compareFunc) {
|
|
23
53
|
let result = []
|
|
24
54
|
|
|
@@ -94,6 +124,9 @@ export function stableSort(array, compareFunc) {
|
|
|
94
124
|
|
|
95
125
|
/**
|
|
96
126
|
* Perform a sort on an array, *in place*, using a key extraction function.
|
|
127
|
+
*
|
|
128
|
+
* As with `Array#sort`, `undefined` entries and holes end up at the end of the array, in that order, and the key
|
|
129
|
+
* extraction function is not called for them. It is called once per remaining element.
|
|
97
130
|
* @alias qui.utils.array.sortKey
|
|
98
131
|
* @param {Array} array
|
|
99
132
|
* @param {Function} func key extraction function; will be called with each element as parameter and is expected to
|
|
@@ -103,11 +136,14 @@ export function stableSort(array, compareFunc) {
|
|
|
103
136
|
* @returns {Array} the array
|
|
104
137
|
*/
|
|
105
138
|
export function sortKey(array, func, desc = false, thisArg = null) {
|
|
106
|
-
return array
|
|
139
|
+
return sortPairs(array, func, desc, thisArg, (pairs, compareFunc) => pairs.sort(compareFunc))
|
|
107
140
|
}
|
|
108
141
|
|
|
109
142
|
/**
|
|
110
143
|
* Perform a stable sort on an array, *in place*, using a key extraction function.
|
|
144
|
+
*
|
|
145
|
+
* As with `Array#sort`, `undefined` entries and holes end up at the end of the array, in that order, and the key
|
|
146
|
+
* extraction function is not called for them. It is called once per remaining element.
|
|
111
147
|
* @alias qui.utils.array.stableSortKey
|
|
112
148
|
* @param {Array} array
|
|
113
149
|
* @param {Function} func key extraction function; will be called with each element as parameter and is expected to
|
|
@@ -117,7 +153,7 @@ export function sortKey(array, func, desc = false, thisArg = null) {
|
|
|
117
153
|
* @returns {Array} the array
|
|
118
154
|
*/
|
|
119
155
|
export function stableSortKey(array, func, desc = false, thisArg = null) {
|
|
120
|
-
return
|
|
156
|
+
return sortPairs(array, func, desc, thisArg, stableSort)
|
|
121
157
|
}
|
|
122
158
|
|
|
123
159
|
/**
|
|
@@ -40,28 +40,39 @@ const IconLabelViewMixin = Mixin((superclass = Object) => {
|
|
|
40
40
|
* @returns {jQuery}
|
|
41
41
|
*/
|
|
42
42
|
makeIconLabelContainer() {
|
|
43
|
-
|
|
43
|
+
/* Built with plain DOM calls: five elements per view, and jQuery's element and append helpers were about
|
|
44
|
+
* a fifth of what the hub's 448-row port list cost to build. The three elements kept as fields stay
|
|
45
|
+
* jQuery objects, because the icon and label setters work on them. */
|
|
46
|
+
let container = document.createElement('div')
|
|
47
|
+
container.className = 'qui-icon-label-view'
|
|
44
48
|
|
|
45
49
|
if (this._clickable) {
|
|
46
|
-
container.
|
|
50
|
+
container.classList.add('qui-base-button')
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
let iconElement = document.createElement('div')
|
|
54
|
+
iconElement.className = 'qui-icon'
|
|
55
|
+
this._iconElement = $(iconElement)
|
|
50
56
|
this._applyIcon(this._icon, this._iconElement)
|
|
51
|
-
container.
|
|
57
|
+
container.appendChild(iconElement)
|
|
52
58
|
|
|
53
|
-
let labelsContainer =
|
|
54
|
-
|
|
59
|
+
let labelsContainer = document.createElement('div')
|
|
60
|
+
labelsContainer.className = 'labels'
|
|
61
|
+
container.appendChild(labelsContainer)
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
let labelElement = document.createElement('div')
|
|
64
|
+
labelElement.className = 'label'
|
|
65
|
+
this._labelElement = $(labelElement)
|
|
57
66
|
this.setLabel(this._label)
|
|
58
|
-
labelsContainer.
|
|
67
|
+
labelsContainer.appendChild(labelElement)
|
|
59
68
|
|
|
60
|
-
|
|
69
|
+
let subLabelElement = document.createElement('div')
|
|
70
|
+
subLabelElement.className = 'sub-label'
|
|
71
|
+
this._subLabelElement = $(subLabelElement)
|
|
61
72
|
this.setSubLabel(this._subLabel)
|
|
62
|
-
labelsContainer.
|
|
73
|
+
labelsContainer.appendChild(subLabelElement)
|
|
63
74
|
|
|
64
|
-
return container
|
|
75
|
+
return $(container)
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
/**
|
|
@@ -113,23 +113,35 @@ $.widget('qui.progressdisk', $.qui.basewidget, {
|
|
|
113
113
|
let iRadius = oRadius - thickness / 2
|
|
114
114
|
let color = Theme.getColor(this.options.color)
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
116
|
+
/* This runs on every value change, but the size only follows the radius option. jQuery's width() and height()
|
|
117
|
+
* each read the computed style, which came to about 400 ms while a 448-row list was being built. */
|
|
118
|
+
if (this._appliedWidth !== width) {
|
|
119
|
+
this._appliedWidth = width
|
|
120
|
+
|
|
121
|
+
this.element.width(width)
|
|
122
|
+
this.element.height(width)
|
|
123
|
+
this.element.css('line-height', width)
|
|
124
|
+
|
|
125
|
+
this._svg.setAttribute('width', width)
|
|
126
|
+
this._svg.setAttribute('height', width)
|
|
127
|
+
this._svg.setAttribute('viewBox', `0 0 ${fWidth} ${fWidth}`)
|
|
128
|
+
|
|
129
|
+
this._ring.setAttribute('cx', oRadius.toString())
|
|
130
|
+
this._ring.setAttribute('cy', oRadius.toString())
|
|
131
|
+
this._ring.setAttribute('rx', iRadius.toString())
|
|
132
|
+
this._ring.setAttribute('ry', iRadius.toString())
|
|
133
|
+
this._ring.setAttribute('stroke-width', thickness.toString())
|
|
134
|
+
|
|
135
|
+
this._cursor.setAttribute('stroke-width', thickness.toString())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* The colour is still looked up every time, so a theme change lands on the next value. */
|
|
139
|
+
if (this._appliedColor !== color) {
|
|
140
|
+
this._appliedColor = color
|
|
141
|
+
|
|
142
|
+
this._ring.setAttribute('stroke', color)
|
|
143
|
+
this._cursor.setAttribute('stroke', color)
|
|
144
|
+
}
|
|
133
145
|
|
|
134
146
|
if (this._curVal >= 0) {
|
|
135
147
|
this._updateSVGPercent(iRadius, oRadius)
|
package/less/base.less
CHANGED
|
@@ -73,6 +73,10 @@ body {
|
|
|
73
73
|
overflow-x: hidden;
|
|
74
74
|
transition: opacity @transition-duration linear;
|
|
75
75
|
line-height: 1.15em;
|
|
76
|
+
|
|
77
|
+
/* A pixel-moving filter, even one that changes nothing, keeps Chrome from running background-color transitions on
|
|
78
|
+
* the compositor, which sometimes shows a transition's first frame again once it ends. */
|
|
79
|
+
filter: blur(0);
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
body.disable-transitions * {
|
|
@@ -57,53 +57,15 @@ div.qui-icon-label-view {
|
|
|
57
57
|
&.qui-base-button {
|
|
58
58
|
background: @background-color;
|
|
59
59
|
color: @interactive-color;
|
|
60
|
-
transition: color @transition-duration ease,
|
|
61
|
-
opacity @transition-duration ease;
|
|
62
|
-
|
|
63
|
-
/* The selected fill and the pressed shade are overlays faded by opacity, which the compositor animates without
|
|
64
|
-
* repainting the row. Chrome sometimes shows a background transition's first frame again once it ends. */
|
|
65
|
-
position: relative;
|
|
66
|
-
z-index: 0;
|
|
67
|
-
|
|
68
|
-
/* Negative z-index keeps both overlays above the row's background but below its content */
|
|
69
|
-
&::before,
|
|
70
|
-
&::after {
|
|
71
|
-
content: '';
|
|
72
|
-
position: absolute;
|
|
73
|
-
top: 0;
|
|
74
|
-
right: 0;
|
|
75
|
-
bottom: 0;
|
|
76
|
-
left: 0;
|
|
77
|
-
opacity: 0;
|
|
78
|
-
transition: opacity @transition-duration ease;
|
|
79
|
-
pointer-events: none;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
&::before {
|
|
83
|
-
background: @interactive-color;
|
|
84
|
-
z-index: -2;
|
|
85
|
-
}
|
|
86
60
|
|
|
87
|
-
|
|
61
|
+
&.active {
|
|
88
62
|
background: @background-active-color;
|
|
89
|
-
z-index: -1;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
&.active::after {
|
|
93
|
-
opacity: 1;
|
|
94
63
|
}
|
|
95
64
|
|
|
96
65
|
&.selected {
|
|
66
|
+
background: @interactive-color;
|
|
97
67
|
color: @background-color;
|
|
98
68
|
|
|
99
|
-
&::before {
|
|
100
|
-
opacity: 1;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
&::after {
|
|
104
|
-
background: @interactive-active-color;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
69
|
& > div.labels > div.sub-label {
|
|
108
70
|
color: @background-color;
|
|
109
71
|
background-color: fade(@interactive-active-color, 50%);
|
|
@@ -111,6 +73,11 @@ div.qui-icon-label-view {
|
|
|
111
73
|
|
|
112
74
|
}
|
|
113
75
|
|
|
76
|
+
&.active.selected {
|
|
77
|
+
background: @interactive-active-color;
|
|
78
|
+
color: @background-color;
|
|
79
|
+
}
|
|
80
|
+
|
|
114
81
|
}
|
|
115
82
|
|
|
116
83
|
}
|
package/less/icons.less
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
.qui-icon {
|
|
6
6
|
display: inline-block;
|
|
7
7
|
position: relative;
|
|
8
|
+
|
|
9
|
+
/* Keeps the z-indexes of the state layers and the decoration inside the icon, so they cannot rise above a sticky
|
|
10
|
+
* header or an overlay that the icon scrolls under */
|
|
11
|
+
z-index: 0;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
.qui-icon > div.qui-icon-normal,
|
package/less/lists.less
CHANGED
|
@@ -8,6 +8,14 @@ div.qui-list {
|
|
|
8
8
|
background: @background-color;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/* A sticky child sticks inside its nearest ancestor that clips. `clip` clips like `hidden` without making a scroll
|
|
12
|
+
* container, so the search box sticks to the scrolling page instead; older browsers keep `hidden` and scroll it away. */
|
|
13
|
+
div.qui-list.qui-structured-view,
|
|
14
|
+
div.qui-list > div.qui-structured-view-body {
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
overflow: clip;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
|
|
12
20
|
/* Children */
|
|
13
21
|
|
|
@@ -55,8 +63,48 @@ div.qui-list-child.qui-list-item {
|
|
|
55
63
|
|
|
56
64
|
/* Search */
|
|
57
65
|
|
|
66
|
+
div.qui-list-child.qui-list-search {
|
|
67
|
+
/* Stays at the top of the page while the rows scroll under it */
|
|
68
|
+
position: sticky;
|
|
69
|
+
top: 0;
|
|
70
|
+
z-index: 1;
|
|
71
|
+
background: @background-color;
|
|
72
|
+
border-bottom-color: @interactive-color;
|
|
73
|
+
|
|
74
|
+
/* Casts the top bar's shadow on the rows while stuck; browsers without scroll-state queries just show none */
|
|
75
|
+
container-type: scroll-state;
|
|
76
|
+
overflow: visible;
|
|
77
|
+
|
|
78
|
+
&::after {
|
|
79
|
+
content: '';
|
|
80
|
+
position: absolute;
|
|
81
|
+
top: 0;
|
|
82
|
+
right: 0;
|
|
83
|
+
bottom: -0.0625em; /* Below the border, like the top bar's shadow */
|
|
84
|
+
left: 0;
|
|
85
|
+
z-index: -1;
|
|
86
|
+
box-shadow: 0 0.3125em 0.5em -0.3125em @bar-shadow-color;
|
|
87
|
+
opacity: 0;
|
|
88
|
+
transition: opacity @transition-duration ease;
|
|
89
|
+
pointer-events: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* The focused field fills with the interactive color, so a line of that color would merge with a selected row
|
|
93
|
+
* below it */
|
|
94
|
+
&:focus-within {
|
|
95
|
+
border-bottom-color: @border-color;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@container scroll-state(stuck: top) {
|
|
100
|
+
div.qui-list-child.qui-list-search::after {
|
|
101
|
+
opacity: 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
58
105
|
div.qui-list-child.qui-list-search > div.qui-list-search-wrapper {
|
|
59
106
|
position: relative;
|
|
107
|
+
overflow: hidden; /* Clips the field's focus ring, now that the search box lets its shadow out */
|
|
60
108
|
|
|
61
109
|
& > input[type=text] {
|
|
62
110
|
width: 100%;
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -24,14 +24,14 @@ function getQueryArgument(name, def = null) {
|
|
|
24
24
|
if (queryArguments == null) {
|
|
25
25
|
let queryString = self.location.search.substring(1)
|
|
26
26
|
queryArguments = new Map(queryString.split('&').map(function (keyValuePair) {
|
|
27
|
-
let splits = keyValuePair.split('=')
|
|
28
|
-
let key = decodeURIComponent(splits[0])
|
|
29
|
-
let value = decodeURIComponent(splits[1])
|
|
27
|
+
let splits = keyValuePair.split('=')
|
|
28
|
+
let key = decodeURIComponent(splits[0])
|
|
29
|
+
let value = decodeURIComponent(splits[1])
|
|
30
30
|
if (value.indexOf(',') >= 0) {
|
|
31
|
-
value = value.split(',')
|
|
31
|
+
value = value.split(',')
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
return [key, value]
|
|
34
|
+
return [key, value]
|
|
35
35
|
}))
|
|
36
36
|
}
|
|
37
37
|
|