@qtoggle/qui 1.20.0-alpha.3 → 1.20.0-alpha.5
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/less/global-glass.less +1 -1
- package/less/lists.less +9 -4
- package/less/main-ui.less +9 -3
- package/less/pages/page.less +28 -2
- package/less/widgets/check-button.less +1 -1
- package/less/widgets/common-buttons.less +5 -2
- package/package.json +3 -1
- package/pyproject.toml +1 -1
- package/qui/templates/manifest.json +12 -12
- package/qui/templates/service-worker.js +20 -9
- package/webpack/webpack-common.js +18 -2
package/less/global-glass.less
CHANGED
|
@@ -9,8 +9,8 @@ div.qui-global-glass {
|
|
|
9
9
|
left: 0;
|
|
10
10
|
right: 0;
|
|
11
11
|
bottom: 0;
|
|
12
|
+
/* backdrop-filter is set once below and never changes, so listing it here only ever cost a property to watch */
|
|
12
13
|
transition: opacity @transition-duration ease,
|
|
13
|
-
backdrop-filter @transition-duration linear,
|
|
14
14
|
margin-top @transition-duration linear;
|
|
15
15
|
opacity: 0;
|
|
16
16
|
display: flex;
|
package/less/lists.less
CHANGED
|
@@ -29,10 +29,15 @@ div.qui-list-child {
|
|
|
29
29
|
|
|
30
30
|
div.qui-list-child.qui-list-item {
|
|
31
31
|
|
|
32
|
-
/*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
32
|
+
/* No `contain` here, deliberately. Layout or paint containment makes each row an independent unit for pixel
|
|
33
|
+
* snapping, and the separator below is a 0.0625em border -- one device pixel at the default font size, but a
|
|
34
|
+
* fractional one as soon as the page is zoomed or the root font size is not 16px. Snapped per row rather than
|
|
35
|
+
* across the list, some of those borders round away to nothing and the separator simply vanishes for a few rows.
|
|
36
|
+
* Reproduced at a 53.6px row pitch: 7 of 10 separators drawn with containment, 10 of 10 without. `contain: paint`
|
|
37
|
+
* alone and `contain: layout style` alone each do it; only `contain: style` is safe, and that buys nothing.
|
|
38
|
+
*
|
|
39
|
+
* The off-screen saving is not lost: content-visibility on the icon-label view below implies containment on that
|
|
40
|
+
* element, which carries no border of its own. */
|
|
36
41
|
|
|
37
42
|
&.hidden {
|
|
38
43
|
opacity: 0;
|
package/less/main-ui.less
CHANGED
|
@@ -24,11 +24,13 @@ div.qui-main-container-glass {
|
|
|
24
24
|
right: 0;
|
|
25
25
|
bottom: 0;
|
|
26
26
|
left: 0;
|
|
27
|
-
|
|
28
|
-
}
|
|
27
|
+
opacity: 0;
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
/* The filter is constant and only the opacity of the filtered layer is animated. Transitioning backdrop-filter
|
|
30
|
+
* itself gives the compositor a different filter to apply on every frame, across the whole viewport, and nothing
|
|
31
|
+
* it can cache. There is no cost while hidden: VisibilityManager sets display: none once the fade has finished. */
|
|
31
32
|
backdrop-filter: @modal-background-filter;
|
|
33
|
+
transition: opacity @transition-duration linear;
|
|
32
34
|
|
|
33
35
|
/* Workaround for Firefox that doesn't support backdrop-filter property */
|
|
34
36
|
@supports (-moz-appearance:none) {
|
|
@@ -36,6 +38,10 @@ div.qui-main-container-glass.visible {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
div.qui-main-container-glass.visible {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
div.qui-pages-container {
|
|
40
46
|
height: 100%;
|
|
41
47
|
}
|
package/less/pages/page.less
CHANGED
|
@@ -27,10 +27,36 @@ div.qui-page.visible {
|
|
|
27
27
|
z-index: 1;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/* On a small screen a page is always exactly as wide as its container, so translateX(100%) lands in the very same
|
|
31
|
+
* place as left: 100% -- but on the compositor, instead of relayouting and repainting both page subtrees on every
|
|
32
|
+
* frame of the slide.
|
|
33
|
+
*
|
|
34
|
+
* The resting state is deliberately `transform: none` and not `translateX(0)`: any transform other than `none` makes
|
|
35
|
+
* the page a containing block for its `position: fixed` descendants, which would unpin the form button bar (see
|
|
36
|
+
* forms/form.less). Interpolating out of `none` animates perfectly well, and while a page is actually sliding, its
|
|
37
|
+
* button bar travelling with it is what one wants anyway.
|
|
38
|
+
*
|
|
39
|
+
* The desktop column layout deliberately stays on `left`/`width`: there a page is narrower than its container, so
|
|
40
|
+
* translateX(100%) would not carry it clear of the viewport, and the slide distance would have to be computed per
|
|
41
|
+
* page against its own width. */
|
|
42
|
+
body.small-screen > div.qui-main-container > div.qui-pages-container > div.qui-page,
|
|
43
|
+
body.small-screen > div.qui-global-glass > div.qui-global-glass-container > div.qui-page {
|
|
32
44
|
width: 100% !important;
|
|
33
45
|
left: 0 !important;
|
|
46
|
+
transform: translateX(100%);
|
|
47
|
+
transition: opacity @transition-duration ease-out,
|
|
48
|
+
transform @transition-duration ease;
|
|
49
|
+
|
|
50
|
+
&.attached {
|
|
51
|
+
transform: translateX(-100%);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Popups fade in place; they never slide */
|
|
55
|
+
&.popup,
|
|
56
|
+
&.visible {
|
|
57
|
+
transform: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
}
|
|
35
61
|
|
|
36
62
|
div.qui-page.current {
|
|
@@ -20,7 +20,7 @@ div.qui-check-button {
|
|
|
20
20
|
transition+: background @transition-duration ease,
|
|
21
21
|
color @transition-duration ease,
|
|
22
22
|
opacity @transition-duration ease,
|
|
23
|
-
border @transition-duration ease;
|
|
23
|
+
border-color @transition-duration ease;
|
|
24
24
|
.qui-focusable-widget();
|
|
25
25
|
|
|
26
26
|
&:FOCUS {
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
@import (reference) "common";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
/* border-color, not the border shorthand: the width and style of these borders are set once and never animate, so
|
|
8
|
+
* naming the colour keeps the fade while leaving border-width and border-style -- the two that would cost layout --
|
|
9
|
+
* out of the transition entirely. */
|
|
7
10
|
.qui-base-button {
|
|
8
11
|
transition: background @transition-duration ease,
|
|
9
12
|
color @transition-duration ease,
|
|
10
13
|
opacity @transition-duration ease,
|
|
11
|
-
border @transition-duration ease;
|
|
14
|
+
border-color @transition-duration ease;
|
|
12
15
|
box-sizing: border-box;
|
|
13
16
|
cursor: pointer;
|
|
14
17
|
.noselect;
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
transition+: background @transition-duration ease,
|
|
74
77
|
color @transition-duration ease,
|
|
75
78
|
opacity @transition-duration ease,
|
|
76
|
-
border @transition-duration ease;
|
|
79
|
+
border-color @transition-duration ease;
|
|
77
80
|
.qui-focusable-widget();
|
|
78
81
|
|
|
79
82
|
&:FOCUS {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qtoggle/qui",
|
|
3
3
|
"description": "A JavaScript UI library with batteries included.",
|
|
4
|
-
"version": "1.20.0-alpha.
|
|
4
|
+
"version": "1.20.0-alpha.5",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Calin Crisan",
|
|
7
7
|
"email": "ccrisan@gmail.com"
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"keywords": [],
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@babel/runtime": "^7.29.7",
|
|
15
16
|
"jquery": "^3",
|
|
16
17
|
"jquery-mousewheel": "*",
|
|
17
18
|
"jquery-ui": "^1",
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"@babel/core": "^7.27.0",
|
|
23
24
|
"@babel/eslint-parser": "^7.27.0",
|
|
24
25
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
26
|
+
"@babel/plugin-transform-runtime": "^7.29.7",
|
|
25
27
|
"@babel/preset-env": "^7.27.0",
|
|
26
28
|
"babel-loader": "^8",
|
|
27
29
|
"css-loader": "^3",
|
package/pyproject.toml
CHANGED
|
@@ -7,18 +7,18 @@
|
|
|
7
7
|
{% if theme_color -%}"theme_color": "{{ theme_color }}",{%- endif %}
|
|
8
8
|
{% if background_color -%}"background_color": "{{ background_color }}",{%- endif %}
|
|
9
9
|
"icons": [
|
|
10
|
-
{"src": "{{ static_url }}/img/launcher-icon-16.png", "sizes": "16x16", "type": "image/png"},
|
|
11
|
-
{"src": "{{ static_url }}/img/launcher-icon-32.png", "sizes": "32x32", "type": "image/png"},
|
|
12
|
-
{"src": "{{ static_url }}/img/launcher-icon-36.png", "sizes": "36x36", "type": "image/png"},
|
|
13
|
-
{"src": "{{ static_url }}/img/launcher-icon-48.png", "sizes": "48x48", "type": "image/png"},
|
|
14
|
-
{"src": "{{ static_url }}/img/launcher-icon-64.png", "sizes": "64x64", "type": "image/png"},
|
|
15
|
-
{"src": "{{ static_url }}/img/launcher-icon-72.png", "sizes": "72x72", "type": "image/png"},
|
|
16
|
-
{"src": "{{ static_url }}/img/launcher-icon-96.png", "sizes": "96x96", "type": "image/png"},
|
|
17
|
-
{"src": "{{ static_url }}/img/launcher-icon-144.png", "sizes": "144x144", "type": "image/png"},
|
|
18
|
-
{"src": "{{ static_url }}/img/launcher-icon-192.png", "sizes": "192x192", "type": "image/png"},
|
|
19
|
-
{"src": "{{ static_url }}/img/launcher-icon-256.png", "sizes": "256x256", "type": "image/png"},
|
|
20
|
-
{"src": "{{ static_url }}/img/launcher-icon-384.png", "sizes": "384x384", "type": "image/png"},
|
|
21
|
-
{"src": "{{ static_url }}/img/launcher-icon-512.png", "sizes": "512x512", "type": "image/png"}
|
|
10
|
+
{"src": "{{ static_url }}/img/launcher-icon-16.png?h={{ build_hash }}", "sizes": "16x16", "type": "image/png"},
|
|
11
|
+
{"src": "{{ static_url }}/img/launcher-icon-32.png?h={{ build_hash }}", "sizes": "32x32", "type": "image/png"},
|
|
12
|
+
{"src": "{{ static_url }}/img/launcher-icon-36.png?h={{ build_hash }}", "sizes": "36x36", "type": "image/png"},
|
|
13
|
+
{"src": "{{ static_url }}/img/launcher-icon-48.png?h={{ build_hash }}", "sizes": "48x48", "type": "image/png"},
|
|
14
|
+
{"src": "{{ static_url }}/img/launcher-icon-64.png?h={{ build_hash }}", "sizes": "64x64", "type": "image/png"},
|
|
15
|
+
{"src": "{{ static_url }}/img/launcher-icon-72.png?h={{ build_hash }}", "sizes": "72x72", "type": "image/png"},
|
|
16
|
+
{"src": "{{ static_url }}/img/launcher-icon-96.png?h={{ build_hash }}", "sizes": "96x96", "type": "image/png"},
|
|
17
|
+
{"src": "{{ static_url }}/img/launcher-icon-144.png?h={{ build_hash }}", "sizes": "144x144", "type": "image/png"},
|
|
18
|
+
{"src": "{{ static_url }}/img/launcher-icon-192.png?h={{ build_hash }}", "sizes": "192x192", "type": "image/png"},
|
|
19
|
+
{"src": "{{ static_url }}/img/launcher-icon-256.png?h={{ build_hash }}", "sizes": "256x256", "type": "image/png"},
|
|
20
|
+
{"src": "{{ static_url }}/img/launcher-icon-384.png?h={{ build_hash }}", "sizes": "384x384", "type": "image/png"},
|
|
21
|
+
{"src": "{{ static_url }}/img/launcher-icon-512.png?h={{ build_hash }}", "sizes": "512x512", "type": "image/png"}
|
|
22
22
|
],
|
|
23
23
|
"start_url": ".",
|
|
24
24
|
"display": "standalone"
|
|
@@ -99,7 +99,9 @@ function sendClientMessage(message, uncontrolled = false) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function shouldCacheRequest(request) {
|
|
102
|
-
|
|
102
|
+
/* Match on the path alone. Every asset URL carries a ?h=<build hash> query, which an extension pattern anchored
|
|
103
|
+
* at the end of the string would never match. */
|
|
104
|
+
return new URL(request.url).pathname.match(cacheURLRegex) && (request.method === 'GET')
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
function shouldCacheResponse(response) {
|
|
@@ -113,19 +115,21 @@ self.addEventListener('activate', function (event) {
|
|
|
113
115
|
|
|
114
116
|
function deleteCache(name) {
|
|
115
117
|
logDebug(`deleting cache ${name}`)
|
|
116
|
-
caches.delete(name)
|
|
118
|
+
return caches.delete(name)
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
event.waitUntil(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
Promise.all([
|
|
123
|
+
/* Become available to all pages */
|
|
124
|
+
self.clients.claim(),
|
|
125
|
+
/* Clear the caches left behind by other builds -- but not the one this build is about to fill, whose
|
|
126
|
+
* name also starts with the app name */
|
|
123
127
|
caches.keys().then(function (cacheNames) {
|
|
124
128
|
return Promise.all(cacheNames
|
|
125
|
-
.filter(name => name.startsWith(`${appName}-`))
|
|
129
|
+
.filter(name => name.startsWith(`${appName}-`) && name !== cacheName)
|
|
126
130
|
.map(name => deleteCache(name)))
|
|
127
131
|
})
|
|
128
|
-
|
|
132
|
+
])
|
|
129
133
|
)
|
|
130
134
|
})
|
|
131
135
|
|
|
@@ -138,8 +142,9 @@ self.addEventListener('fetch', function (event) {
|
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
let url = event.request.url
|
|
141
|
-
|
|
142
|
-
if (
|
|
145
|
+
let requestedHash = new URL(url).searchParams.get('h')
|
|
146
|
+
if (requestedHash == null) {
|
|
147
|
+
/* Ensure URL has build hash */
|
|
143
148
|
if (url.includes('?')) {
|
|
144
149
|
url += `&h=${buildHash}`
|
|
145
150
|
}
|
|
@@ -147,6 +152,12 @@ self.addEventListener('fetch', function (event) {
|
|
|
147
152
|
url += `?h=${buildHash}`
|
|
148
153
|
}
|
|
149
154
|
}
|
|
155
|
+
else if (requestedHash !== buildHash) {
|
|
156
|
+
/* A URL left over from another build, most likely requested by a page that was already open when this worker
|
|
157
|
+
* took over. Send it to the network untouched rather than filling this build's cache with a key that nothing
|
|
158
|
+
* is going to ask for again. */
|
|
159
|
+
return
|
|
160
|
+
}
|
|
150
161
|
|
|
151
162
|
let request = new Request(url, event.request)
|
|
152
163
|
|
|
@@ -211,8 +211,24 @@ function makeJSRule({type, appFullPath}) {
|
|
|
211
211
|
{
|
|
212
212
|
loader: 'babel-loader',
|
|
213
213
|
options: {
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
/* The entry is a <script type="module"> with no nomodule fallback, so a browser that cannot run
|
|
215
|
+
* ES modules never gets this far. Targeting them is the honest floor -- and it is the floor the
|
|
216
|
+
* markup already enforces, so it drops no browser that could previously run the app.
|
|
217
|
+
*
|
|
218
|
+
* Set here rather than in a .browserslistrc because the build compiles QUI's own sources from a
|
|
219
|
+
* sibling checkout outside the app's tree, where browserslist config resolution would depend on
|
|
220
|
+
* the working directory, and because it keeps one source of truth for both repos. */
|
|
221
|
+
presets: [['@babel/preset-env', {targets: {esmodules: true}}]],
|
|
222
|
+
plugins: [
|
|
223
|
+
'@babel/plugin-proposal-class-properties',
|
|
224
|
+
/* Import the remaining helpers from a shared runtime rather than inlining a copy of
|
|
225
|
+
* each into every module that needs one. useESModules defaults to false, which would pull
|
|
226
|
+
* the CommonJS builds of those helpers and cost webpack its tree shaking and module
|
|
227
|
+
* concatenation across them; "auto" takes the answer from the bundler's own declared
|
|
228
|
+
* capability instead of asserting it here. (Babel 8 drops the option and always
|
|
229
|
+
* auto-detects, so it comes out again with that upgrade.) */
|
|
230
|
+
['@babel/plugin-transform-runtime', {useESModules: 'auto'}]
|
|
231
|
+
]
|
|
216
232
|
}
|
|
217
233
|
}
|
|
218
234
|
]
|