@threadlabs/looma 0.39.1 → 0.39.3
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/components/ui-button/ui-button.html +24 -3
- package/components/ui-checkbox/ui-checkbox.html +34 -18
- package/components/ui-combobox/ui-combobox.html +8 -3
- package/components/ui-combobox/ui-combobox.js +3 -2
- package/components/ui-dialog/ui-dialog.html +6 -52
- package/components/ui-input/ui-input.html +21 -3
- package/components/ui-input-group/ui-input-group.html +1 -0
- package/components/ui-light-editor/ui-light-editor.html +26 -25
- package/components/ui-select/ui-select.html +10 -3
- package/dist/index.js +1 -1
- package/docs/README.md +18 -7
- package/docs/catalog.json +2 -26
- package/docs/customizer-render.mjs +37 -33
- package/docs/customizer.js +82 -47
- package/docs/site.mjs +10 -6
- package/docs/theme-session.mjs +7 -8
- package/docs/theme.mjs +10 -22
- package/package.json +1 -1
- package/styles/ui-button.css +24 -1
- package/styles/ui-checkbox.css +39 -19
- package/styles/ui-combobox.css +16 -6
- package/styles/ui-dialog.css +6 -107
- package/styles/ui-input-group.css +1 -0
- package/styles/ui-input.css +24 -3
- package/styles/ui-light-editor.css +111 -0
- package/styles/ui-select.css +10 -3
- package/theme-system.css +4 -1
- package/tokens.css +9 -2
- package/vanilla/UiButton.js +14 -12
- package/vanilla/UiDialog.js +2 -2
- package/vanilla/UiLightEditor.js +2 -2
- package/vue/UiButton.vue +28 -7
- package/vue/UiCheckbox.vue +34 -19
- package/vue/UiCombobox.vue +22 -6
- package/vue/UiDialog.d.ts +3 -3
- package/vue/UiDialog.vue +18 -111
- package/vue/UiInput.vue +30 -3
- package/vue/UiInputGroup.vue +1 -0
- package/vue/UiLightEditor.vue +156 -40
- package/vue/UiSelect.vue +16 -3
- package/vue/chunks/UiButton.js +18 -10
- package/vue/chunks/UiCheckbox.js +1 -1
- package/vue/chunks/UiCombobox.js +1 -1
- package/vue/chunks/UiDialog.js +22 -14
- package/vue/chunks/UiInput.js +1 -1
- package/vue/chunks/UiInputGroup.js +1 -1
- package/vue/chunks/UiLightEditor.js +66 -58
- package/vue/chunks/UiSelect.js +1 -1
- package/vue/components.css +337 -160
- package/vue/editor/index.js +46 -36
package/docs/site.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cp, mkdir, readFile, readdir, realpath, rename, rm, symlink, unlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import { discoverComponents, escapeHtml, mergeCatalogs, readCatalog, readJson } from './catalog.mjs';
|
|
@@ -144,7 +144,7 @@ export async function prepareDocumentation({ root, config = {}, catalog }) {
|
|
|
144
144
|
const customizer = path.join(generated, 'customizer.html');
|
|
145
145
|
await write(customizer, customizerResource({ catalog: combined, config, base }, config.recipe));
|
|
146
146
|
const customizerPage = path.join(content, 'customize.md');
|
|
147
|
-
await write(customizerPage, `---\ntitle: Customize\nsidebar: false\noutline: false\nprevious: false\nnext: false\n---\n\n#
|
|
147
|
+
await write(customizerPage, `---\ntitle: Customize\nsidebar: false\noutline: false\nprevious: false\nnext: false\n---\n\n# Customize your theme\n\n<link rel="component" href="${relativeHref(customizerPage, customizer)}">\n<looma-customizer></looma-customizer>\n`);
|
|
148
148
|
await write(path.join(generated, 'catalog.json'), JSON.stringify(combined));
|
|
149
149
|
const redirects = {};
|
|
150
150
|
for (const component of combined.components) {
|
|
@@ -220,7 +220,7 @@ export async function buildDocumentation({ root = process.cwd(), config = {}, ca
|
|
|
220
220
|
});
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
async function isolatePreviewScripts(site, { outDir
|
|
223
|
+
async function isolatePreviewScripts(site, { outDir }) {
|
|
224
224
|
const { build } = await tool('vite', site.root);
|
|
225
225
|
// Preview entries and outputs are independent. Keep compiler memory bounded
|
|
226
226
|
// and retain the build lock until every in-flight bundle has settled.
|
|
@@ -236,9 +236,13 @@ async function isolatePreviewScripts(site, { outDir, writeFile: writeOutput }) {
|
|
|
236
236
|
const input = path.join(outDir, url.slice(site.base.length));
|
|
237
237
|
const bundle = await build({ configFile: false, logLevel: 'silent', build: { write: false, minify: true, lib: { entry: input, name: 'LoomaPreview', formats: ['iife'] }, rollupOptions: { output: { codeSplitting: false } } } });
|
|
238
238
|
const code = (Array.isArray(bundle) ? bundle[0] : bundle).output.find(output => output.type === 'chunk').code;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
// A sandbox has an opaque origin: Safari cannot authorize this runtime via 'self'.
|
|
240
|
+
// Hash the exact inline bytes and run after the preview DOM, preserving isolation.
|
|
241
|
+
const script = code.replace(/<\/script/gi, '<\\/script');
|
|
242
|
+
const hash = createHash('sha256').update(script).digest('base64');
|
|
243
|
+
if (!/<meta\b[^>]*http-equiv="Content-Security-Policy"[^>]*content="[^"]*\bscript-src\b/.test(html)) throw new Error(`Missing script policy for isolated preview ${example.id}.`);
|
|
244
|
+
html = html.replace(/(<meta\b[^>]*http-equiv="Content-Security-Policy"[^>]*content="[^"]*\bscript-src)\b/, `$1 'sha256-${hash}'`);
|
|
245
|
+
html = html.replace(tag, '').replace('</body>', () => `<script>${script}</script></body>`);
|
|
242
246
|
}
|
|
243
247
|
html = html.replace(/<link\b[^>]*rel="modulepreload"[^>]*>/g, '');
|
|
244
248
|
await writeFile(htmlFile, html);
|
package/docs/theme-session.mjs
CHANGED
|
@@ -7,7 +7,7 @@ export const themeEvent = 'looma-documentation-theme';
|
|
|
7
7
|
export function createThemeSession(view, { base, baseline, systemCSS, historyLimit = 100 }) {
|
|
8
8
|
const key = `looma:documentation-theme:v1:${base}`;
|
|
9
9
|
let original = validateRecipe(baseline);
|
|
10
|
-
let selected, draft
|
|
10
|
+
let selected, draft;
|
|
11
11
|
const past = [], future = [];
|
|
12
12
|
let portableCSS = '';
|
|
13
13
|
let storageAvailable = true;
|
|
@@ -17,7 +17,7 @@ export function createThemeSession(view, { base, baseline, systemCSS, historyLim
|
|
|
17
17
|
const read = value => { try { return value ? validateRecipe(JSON.parse(value)) : undefined; } catch { return undefined; } };
|
|
18
18
|
try { selected = read(view.localStorage.getItem(key)); } catch { storageAvailable = false; }
|
|
19
19
|
const paint = () => {
|
|
20
|
-
const displayed =
|
|
20
|
+
const displayed = draft ?? selected;
|
|
21
21
|
portableCSS = displayed ? themeCSS(displayed, systemCSS) : '';
|
|
22
22
|
view.document.documentElement.toggleAttribute('data-looma-documentation-theme', Boolean(displayed));
|
|
23
23
|
style.textContent = displayed ? themeCSS(displayed, systemCSS, { scope: 'documentation' }) : '';
|
|
@@ -42,22 +42,21 @@ export function createThemeSession(view, { base, baseline, systemCSS, historyLim
|
|
|
42
42
|
preview(recipe) { draft = validateRecipe(recipe); paint(); },
|
|
43
43
|
commit() { if (draft) session.apply(draft); },
|
|
44
44
|
cancel() { draft = undefined; paint(); },
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
redo() { if (!future.length) return; past.push(structuredClone(selected)); selected = future.pop(); draft = comparison = undefined; persist(); },
|
|
45
|
+
undo() { if (!past.length) return; future.push(structuredClone(selected)); selected = past.pop(); draft = undefined; persist(); },
|
|
46
|
+
redo() { if (!future.length) return; past.push(structuredClone(selected)); selected = future.pop(); draft = undefined; persist(); },
|
|
48
47
|
apply(recipe) {
|
|
49
48
|
const next = validateRecipe(recipe);
|
|
50
49
|
if (JSON.stringify(next) !== JSON.stringify(selected)) remember();
|
|
51
|
-
selected = next; draft =
|
|
50
|
+
selected = next; draft = undefined; persist();
|
|
52
51
|
},
|
|
53
|
-
reset() { if (selected !== undefined) remember(); selected = draft =
|
|
52
|
+
reset() { if (selected !== undefined) remember(); selected = draft = undefined; persist(); return structuredClone(original); },
|
|
54
53
|
baseline(recipe) { original = validateRecipe(recipe); },
|
|
55
54
|
};
|
|
56
55
|
view.addEventListener('storage', event => {
|
|
57
56
|
if (event.key !== key && event.key !== null) return;
|
|
58
57
|
const next = read(event.newValue);
|
|
59
58
|
if (event.newValue !== null && !next) return;
|
|
60
|
-
selected = next; draft =
|
|
59
|
+
selected = next; draft = undefined; past.length = future.length = 0; paint();
|
|
61
60
|
});
|
|
62
61
|
view.addEventListener('markupress-header-notice-dismiss', () => session.reset());
|
|
63
62
|
paint();
|
package/docs/theme.mjs
CHANGED
|
@@ -49,9 +49,9 @@ const starting = values => ({ schemaVersion: 1, values: { ...defaultRecipe().val
|
|
|
49
49
|
export const startingPoints = {
|
|
50
50
|
clear: defaultRecipe(),
|
|
51
51
|
bookish: starting({ rhythm: '1.05', action: '#2e1228', canvas: '#f7f7f5', surface: '#fffefd', text: '#0d0c0c', 'body-font': 'Arial, sans-serif', 'display-font': 'Georgia, serif', 'text-weight': '400', 'control-roominess': '2.625', 'scale-contrast': '1.18', 'outer-depth': '1', 'inner-depth': '1', glassiness: '0.8', 'surface-separation': '0.65', translucency: '0.18', 'edge-light': '1', finish: '0.85', glow: '0.3', 'light-spread': '0.3', 'light-strength': '0.9', 'response-time': '150ms', 'response-curve': 'ease' }),
|
|
52
|
-
wiki: starting({ rhythm: '0.
|
|
52
|
+
wiki: starting({ rhythm: '0.95', 'text-weight': '400', 'space-curve': '0.8', action: '#4f46e5', canvas: '#ffffff', surface: '#ffffff', text: '#1a1a1a', 'control-roominess': '2.125', spacing: '0.95', 'scale-contrast': '1.15', 'corner-roundness': '6px', 'outer-depth': '0.35', 'inner-depth': '0', 'response-time': '110ms' }),
|
|
53
53
|
editorial: starting({ rhythm: '1.3', 'text-size': '17px', spacing: '1.12', 'space-curve': '1.4', 'scale-contrast': '1.3', action: '#182c24', canvas: '#eee9dc', surface: '#fffdf5', text: '#182c24', 'corner-roundness': '0px', 'outer-depth': '0', 'inner-depth': '0', 'body-font': 'Georgia, serif', 'display-font': 'Georgia, serif', 'text-weight': '400', 'line-weight': '2px', 'response-time': '90ms', 'response-curve': 'linear' }),
|
|
54
|
-
night: starting({ rhythm: '
|
|
54
|
+
night: starting({ rhythm: '1.05', 'display-font': 'ui-monospace, monospace', 'text-weight': '500', 'control-roominess': '2.375', spacing: '1.05', 'scale-contrast': '1.25', 'space-curve': '1', finish: '0.6', glow: '0.45', glassiness: '0.45', 'surface-separation': '0.25', 'light-x': '0.25', 'light-spread': '0.65', action: '#a7b8ff', canvas: '#11141c', surface: '#202634', text: '#edf1fb', 'corner-roundness': '12px', 'outer-depth': '1.4', 'inner-depth': '0.45', translucency: '0.2', 'edge-light': '0.65', 'response-time': '210ms', 'response-curve': 'cubic-bezier(0.2,0.8,0.2,1)' }),
|
|
55
55
|
garden: starting({ rhythm: '1.2', 'body-font': 'ui-rounded, system-ui, sans-serif', 'display-font': 'Georgia, serif', 'text-weight': '400', 'space-curve': '1.15', finish: '0.15', 'light-spread': '1', action: '#256342', canvas: '#f4edda', surface: '#fff8e7', text: '#173121', 'corner-roundness': '22px', 'outer-depth': '0.35', 'inner-depth': '0', 'edge-light': '0.2', 'control-roominess': '2.875', 'scale-contrast': '1.24', spacing: '1.05', 'response-time': '180ms', 'response-curve': 'cubic-bezier(0.16,1,0.3,1)' }),
|
|
56
56
|
};
|
|
57
57
|
export const startingPointLabels = { clear: 'Clear', bookish: 'Bookish', wiki: 'Wiki', editorial: 'Editorial', night: 'Night', garden: 'Garden' };
|
|
@@ -95,24 +95,6 @@ export function paletteFor(input) {
|
|
|
95
95
|
return Object.entries(themePalettes).find(([, palette]) => paletteKeys.every(key => values[key].toLowerCase() === palette.values[key].toLowerCase()))?.[0] ?? 'custom';
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export const themeVariations = { warmer: 'Warmer', quieter: 'Quieter', expressive: 'More expressive', compact: 'More compact' };
|
|
99
|
-
/** Nearby choices reuse the same bounded semantics; locks protect whole decision families. */
|
|
100
|
-
export function nearbyVariation(input, kind, locks = []) {
|
|
101
|
-
const recipe = validateRecipe(input);
|
|
102
|
-
if (!Object.hasOwn(themeVariations, kind)) throw new Error('Unknown theme variation.');
|
|
103
|
-
const deltas = {
|
|
104
|
-
warmer: { warmth: .15 }, quieter: { 'color-intensity': -.12, glow: -.1, 'outer-depth': -.15 },
|
|
105
|
-
expressive: { finish: .15, 'edge-light': .15, glow: .1 }, compact: { 'control-roominess': -.15, spacing: -.1 },
|
|
106
|
-
}[kind];
|
|
107
|
-
const values = { ...recipe.values };
|
|
108
|
-
for (const [id, delta] of Object.entries(deltas)) {
|
|
109
|
-
const field = themeFields.find(item => item.id === id);
|
|
110
|
-
const family = field.family === 'Colour' ? 'palette' : field.family === 'Material' ? 'material' : 'type';
|
|
111
|
-
if (!locks.includes(family)) values[id] = String(Number(Math.max(field.min, Math.min(field.max, Number(values[id]) + delta)).toFixed(3)));
|
|
112
|
-
}
|
|
113
|
-
return validateRecipe({ ...recipe, values });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
98
|
const scalar = /^[+]?(?:\d+(?:\.\d*)?|\.\d+)$/;
|
|
117
99
|
const length = /^(?:0|[+]?(?:\d+(?:\.\d*)?|\.\d+)(?:px|rem|em|ex|ch|cap|ic|lh|rlh|vw|vh|vi|vb|vmin|vmax|svw|svh|lvw|lvh|dvw|dvh|cm|mm|Q|in|pt|pc))$/;
|
|
118
100
|
const font = /^(?:"[^"\\;{}<>@\x00-\x1f]+"|'[^'\\;{}<>@\x00-\x1f]+'|[\p{L}_-][\p{L}\p{N}_ -]*)(?:\s*,\s*(?:"[^"\\;{}<>@\x00-\x1f]+"|'[^'\\;{}<>@\x00-\x1f]+'|[\p{L}_-][\p{L}\p{N}_ -]*))*$/u;
|
|
@@ -291,15 +273,21 @@ export function themeCSS(input, systemCSS = '', { scope = 'portable' } = {}) {
|
|
|
291
273
|
const selector = scope === 'documentation'
|
|
292
274
|
? ':root[data-looma-documentation-theme], :root[data-looma-documentation-theme] [data-theme], :root[data-looma-documentation-theme] [data-contrast]'
|
|
293
275
|
: ':root, [data-theme], [data-contrast]';
|
|
294
|
-
const
|
|
276
|
+
const recipe = validateRecipe(input);
|
|
277
|
+
const fontURL = googleFontURL(recipe.fonts);
|
|
295
278
|
const imports = fontURL ? `@import url("${fontURL}");\n` : '';
|
|
296
279
|
const light = deriveTheme(input, { mode: 'light' }).tokens;
|
|
297
280
|
const dark = deriveTheme(input, { mode: 'dark' }).tokens;
|
|
298
281
|
const palette = [], geometry = [];
|
|
282
|
+
const relativeLengths = new Map(themeFields.filter(field => field.property && field.type.includes('length') && recipe.values[field.id].endsWith('rem')).map(field => [field.property, recipe.values[field.id]]));
|
|
299
283
|
for (const [name, value] of Object.entries(light)) {
|
|
300
284
|
if (name === 'color-scheme') continue;
|
|
301
285
|
if (/^#[\da-f]{6}$/i.test(value)) palette.push(` ${name}: light-dark(${value}, ${dark[name]});`);
|
|
302
|
-
else
|
|
286
|
+
else {
|
|
287
|
+
const relative = relativeLengths.get(name);
|
|
288
|
+
const resolved = relative ? value.replace(relative, `calc(${parseFloat(relative)} * var(--_ui-root-font-unit, 1rem))`) : value;
|
|
289
|
+
geometry.push(` ${name}: ${resolved};`);
|
|
290
|
+
}
|
|
303
291
|
}
|
|
304
292
|
const geometrySelector = scope === 'documentation' ? ':root[data-looma-documentation-theme]' : ':root';
|
|
305
293
|
const chevron = color => `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='${color.replace('#', '%23')}' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")`;
|
package/package.json
CHANGED
package/styles/ui-button.css
CHANGED
|
@@ -130,6 +130,7 @@
|
|
|
130
130
|
|
|
131
131
|
:scope:where([data-component~="ui-button"]) {
|
|
132
132
|
--_button-square-size: var(--ui-button-min-block-size, var(--ui-control-size-md));
|
|
133
|
+
box-sizing: border-box !important;
|
|
133
134
|
appearance: none;
|
|
134
135
|
min-block-size: var(--ui-button-min-block-size, var(--ui-control-size-md));
|
|
135
136
|
border-style: solid;
|
|
@@ -176,6 +177,21 @@
|
|
|
176
177
|
padding-block: var(--ui-space-0-5) !important;
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
/* Trim the text line itself: an anonymous flex item retains font-metric space in some fonts. */
|
|
181
|
+
.label {
|
|
182
|
+
display: block !important;
|
|
183
|
+
align-content: center !important;
|
|
184
|
+
min-inline-size: 0 !important;
|
|
185
|
+
min-block-size: 1lh !important;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.label:has(svg) {
|
|
189
|
+
display: inline-flex !important;
|
|
190
|
+
align-items: center !important;
|
|
191
|
+
gap: var(--ui-space-2) !important;
|
|
192
|
+
text-box-trim: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
179
195
|
.badge {
|
|
180
196
|
display: inline-flex !important;
|
|
181
197
|
align-items: center !important;
|
|
@@ -187,7 +203,7 @@
|
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
@supports (text-box-trim: trim-both) {
|
|
190
|
-
:
|
|
206
|
+
.label:not(:has(svg)) {
|
|
191
207
|
text-box-trim: trim-both;
|
|
192
208
|
text-box-edge: cap alphabetic;
|
|
193
209
|
}
|
|
@@ -515,6 +531,11 @@
|
|
|
515
531
|
transition: color var(--ui-motion-fast) var(--ui-motion-ease), text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
516
532
|
}
|
|
517
533
|
|
|
534
|
+
:scope:where([data-component~="ui-button"])[data-appearance="link"] .label {
|
|
535
|
+
display: inline !important;
|
|
536
|
+
text-box-trim: none;
|
|
537
|
+
}
|
|
538
|
+
|
|
518
539
|
:scope:where([data-component~="ui-button"])[data-appearance="link"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
519
540
|
color: var(--ui-button-link-hover-text, var(--ui-text-primary));
|
|
520
541
|
text-decoration-color: currentColor;
|
|
@@ -552,6 +573,8 @@
|
|
|
552
573
|
/* A count is an adornment, not another line of label text. Keep the standard control height
|
|
553
574
|
with enough inset for a 24px mark; large content can still grow the button naturally. */
|
|
554
575
|
|
|
576
|
+
/* Trim the text line itself: an anonymous flex item retains font-metric space in some fonts. */
|
|
577
|
+
|
|
555
578
|
@supports (text-box-trim: trim-both) {
|
|
556
579
|
}
|
|
557
580
|
|
package/styles/ui-checkbox.css
CHANGED
|
@@ -103,7 +103,8 @@
|
|
|
103
103
|
|
|
104
104
|
input:where(:not([data-component], [data-slotted]))::after {
|
|
105
105
|
content: "";
|
|
106
|
-
position: absolute;
|
|
106
|
+
position: absolute !important;
|
|
107
|
+
transform: none !important;
|
|
107
108
|
opacity: 0;
|
|
108
109
|
transition: opacity var(--ui-motion-fast) var(--ui-motion-ease);
|
|
109
110
|
}
|
|
@@ -116,43 +117,44 @@
|
|
|
116
117
|
box-shadow: var(--ui-control-inset);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
/*
|
|
120
|
+
/* The SVG's painted bounds are centred in its viewBox, including its round stroke.
|
|
121
|
+
Centre that box directly; rotated border boxes do not centre the visible check. */
|
|
120
122
|
input:checked:where(:not([data-component], [data-slotted]))::after {
|
|
121
|
-
inset:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
border-style: solid;
|
|
128
|
-
border-color: var(--ui-text-on-accent);
|
|
129
|
-
border-width: 0 calc(var(--_ui-checkbox-size) * 0.125 / 1.125) calc(var(--_ui-checkbox-size) * 0.125 / 1.125) 0;
|
|
123
|
+
inset: 50% auto auto 50% !important;
|
|
124
|
+
inline-size: calc(var(--_ui-checkbox-size) * 0.7) !important;
|
|
125
|
+
block-size: calc(var(--_ui-checkbox-size) * 0.7) !important;
|
|
126
|
+
background: var(--ui-text-on-accent);
|
|
127
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 11.5 5 5 9-9'/%3E%3C/svg%3E")
|
|
128
|
+
center / contain no-repeat;
|
|
130
129
|
opacity: 1;
|
|
131
|
-
|
|
130
|
+
translate: -50% -50% !important;
|
|
132
131
|
animation: ui-checkbox-check var(--ui-motion-base) var(--ui-motion-ease-out, cubic-bezier(0.16, 1, 0.3, 1)) both;
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
input:indeterminate:where(:not([data-component], [data-slotted]))::after {
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
mask: none;
|
|
136
|
+
animation: none;
|
|
137
|
+
inset: 50% calc(var(--_ui-checkbox-size) * 0.23 / 1.125) auto !important;
|
|
138
|
+
inline-size: auto !important;
|
|
139
|
+
block-size: calc(var(--_ui-checkbox-size) * 0.125 / 1.125) !important;
|
|
138
140
|
border-radius: var(--ui-radius-round);
|
|
139
141
|
background: var(--ui-text-on-accent);
|
|
140
142
|
opacity: 1;
|
|
141
|
-
|
|
143
|
+
translate: 0 -50% !important;
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
@keyframes ui-checkbox-check {
|
|
145
147
|
from {
|
|
146
148
|
opacity: 0;
|
|
147
|
-
|
|
149
|
+
scale: 0.55;
|
|
148
150
|
}
|
|
149
151
|
65% {
|
|
150
152
|
opacity: 1;
|
|
151
|
-
|
|
153
|
+
scale: 1.12;
|
|
152
154
|
}
|
|
153
155
|
to {
|
|
154
156
|
opacity: 1;
|
|
155
|
-
|
|
157
|
+
scale: 1;
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
160
|
|
|
@@ -163,6 +165,20 @@
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
|
|
168
|
+
@media (forced-colors: active) {
|
|
169
|
+
input:checked,
|
|
170
|
+
input:indeterminate {
|
|
171
|
+
forced-color-adjust: none;
|
|
172
|
+
background: Highlight;
|
|
173
|
+
border-color: Highlight;
|
|
174
|
+
}
|
|
175
|
+
input:checked:where(:not([data-component], [data-slotted]))::after,
|
|
176
|
+
input:indeterminate:where(:not([data-component], [data-slotted]))::after {
|
|
177
|
+
forced-color-adjust: none;
|
|
178
|
+
background: HighlightText;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
166
182
|
input:hover:enabled {
|
|
167
183
|
border-color: var(--ui-accent-solid);
|
|
168
184
|
}
|
|
@@ -220,11 +236,15 @@
|
|
|
220
236
|
|
|
221
237
|
/* The label, and under it an optional line saying what the choice means. */
|
|
222
238
|
|
|
223
|
-
/*
|
|
239
|
+
/* The SVG's painted bounds are centred in its viewBox, including its round stroke.
|
|
240
|
+
Centre that box directly; rotated border boxes do not centre the visible check. */
|
|
224
241
|
|
|
225
242
|
@media (prefers-reduced-motion: reduce) {
|
|
226
243
|
}
|
|
227
244
|
|
|
245
|
+
@media (forced-colors: active) {
|
|
246
|
+
}
|
|
247
|
+
|
|
228
248
|
/* A sized choice keeps its box and aligns instead: the label takes that size's field type size,
|
|
229
249
|
and the first line centres in that control height, so the label shares a row's baseline.
|
|
230
250
|
Multi-line labels and descriptions still grow downward from that first line. */
|
package/styles/ui-combobox.css
CHANGED
|
@@ -198,10 +198,12 @@
|
|
|
198
198
|
color: inherit;
|
|
199
199
|
}
|
|
200
200
|
input {
|
|
201
|
+
--_ui-combobox-inset-block: var(--ui-control-inset-block-md, var(--ui-space-2));
|
|
201
202
|
appearance: none;
|
|
202
203
|
width: 100%;
|
|
203
204
|
min-inline-size: 0;
|
|
204
205
|
min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
|
|
206
|
+
max-block-size: max(calc(var(--_row) - 2 * var(--ui-border-width)), calc(1em * var(--ui-line-height-tight) + 2 * var(--_ui-combobox-inset-block)));
|
|
205
207
|
box-sizing: border-box !important;
|
|
206
208
|
border: 0;
|
|
207
209
|
border-radius: inherit;
|
|
@@ -209,7 +211,7 @@
|
|
|
209
211
|
color: inherit;
|
|
210
212
|
font: inherit;
|
|
211
213
|
line-height: var(--ui-line-height-tight);
|
|
212
|
-
padding: var(--
|
|
214
|
+
padding: var(--_ui-combobox-inset-block) var(--ui-space-3) !important;
|
|
213
215
|
margin: 0 !important;
|
|
214
216
|
outline: none;
|
|
215
217
|
}
|
|
@@ -217,14 +219,16 @@
|
|
|
217
219
|
--_row: var(--ui-control-size-sm);
|
|
218
220
|
}
|
|
219
221
|
:scope:where([data-component~="ui-combobox"])[data-ui-combobox-state~="size=sm"] input {
|
|
220
|
-
|
|
222
|
+
--_ui-combobox-inset-block: var(--ui-control-inset-block-sm, var(--ui-space-1));
|
|
223
|
+
padding: var(--_ui-combobox-inset-block) var(--ui-space-2) !important;
|
|
221
224
|
font-size: var(--ui-font-size-sm);
|
|
222
225
|
}
|
|
223
226
|
:scope:where([data-component~="ui-combobox"])[data-ui-combobox-state~="size=lg"] {
|
|
224
227
|
--_row: var(--ui-control-size-lg);
|
|
225
228
|
}
|
|
226
229
|
:scope:where([data-component~="ui-combobox"])[data-ui-combobox-state~="size=lg"] input {
|
|
227
|
-
|
|
230
|
+
--_ui-combobox-inset-block: var(--ui-control-inset-block-lg, var(--ui-space-3));
|
|
231
|
+
padding: var(--_ui-combobox-inset-block) var(--ui-space-4) !important;
|
|
228
232
|
font-size: var(--ui-font-size-md);
|
|
229
233
|
}
|
|
230
234
|
button {
|
|
@@ -359,6 +363,7 @@
|
|
|
359
363
|
display: none !important;
|
|
360
364
|
}
|
|
361
365
|
:scope:where([data-component~="ui-combobox"])[data-ui-combobox-state~="multiple"] input {
|
|
366
|
+
max-block-size: none;
|
|
362
367
|
flex: 1 0 var(--_ui-combobox-item-input-min-inline-size, 5rem);
|
|
363
368
|
width: auto;
|
|
364
369
|
min-block-size: 1.75rem;
|
|
@@ -617,10 +622,12 @@
|
|
|
617
622
|
color: inherit;
|
|
618
623
|
}
|
|
619
624
|
input:where(:scope, :scope *) {
|
|
625
|
+
--_ui-combobox-inset-block: var(--ui-control-inset-block-md, var(--ui-space-2));
|
|
620
626
|
appearance: none;
|
|
621
627
|
width: 100%;
|
|
622
628
|
min-inline-size: 0;
|
|
623
629
|
min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
|
|
630
|
+
max-block-size: max(calc(var(--_row) - 2 * var(--ui-border-width)), calc(1em * var(--ui-line-height-tight) + 2 * var(--_ui-combobox-inset-block)));
|
|
624
631
|
box-sizing: border-box !important;
|
|
625
632
|
border: 0;
|
|
626
633
|
border-radius: inherit;
|
|
@@ -628,7 +635,7 @@
|
|
|
628
635
|
color: inherit;
|
|
629
636
|
font: inherit;
|
|
630
637
|
line-height: var(--ui-line-height-tight);
|
|
631
|
-
padding: var(--
|
|
638
|
+
padding: var(--_ui-combobox-inset-block) var(--ui-space-3) !important;
|
|
632
639
|
margin: 0 !important;
|
|
633
640
|
outline: none;
|
|
634
641
|
}
|
|
@@ -636,14 +643,16 @@
|
|
|
636
643
|
--_row: var(--ui-control-size-sm);
|
|
637
644
|
}
|
|
638
645
|
:where([data-component~="ui-combobox"])[data-ui-combobox-state~="size=sm"] input:where(:scope, :not([data-component], [data-slotted]:not([data-slotted~="ui-combobox"]))) {
|
|
639
|
-
|
|
646
|
+
--_ui-combobox-inset-block: var(--ui-control-inset-block-sm, var(--ui-space-1));
|
|
647
|
+
padding: var(--_ui-combobox-inset-block) var(--ui-space-2) !important;
|
|
640
648
|
font-size: var(--ui-font-size-sm);
|
|
641
649
|
}
|
|
642
650
|
:where([data-component~="ui-combobox"])[data-ui-combobox-state~="size=lg"]:where(:scope, :not([data-component], [data-slotted]:not([data-slotted~="ui-combobox"]))) {
|
|
643
651
|
--_row: var(--ui-control-size-lg);
|
|
644
652
|
}
|
|
645
653
|
:where([data-component~="ui-combobox"])[data-ui-combobox-state~="size=lg"] input:where(:scope, :not([data-component], [data-slotted]:not([data-slotted~="ui-combobox"]))) {
|
|
646
|
-
|
|
654
|
+
--_ui-combobox-inset-block: var(--ui-control-inset-block-lg, var(--ui-space-3));
|
|
655
|
+
padding: var(--_ui-combobox-inset-block) var(--ui-space-4) !important;
|
|
647
656
|
font-size: var(--ui-font-size-md);
|
|
648
657
|
}
|
|
649
658
|
button:where(:scope, :scope *) {
|
|
@@ -776,6 +785,7 @@
|
|
|
776
785
|
display: none !important;
|
|
777
786
|
}
|
|
778
787
|
:where([data-component~="ui-combobox"])[data-ui-combobox-state~="multiple"] input:where(:scope, :not([data-component], [data-slotted]:not([data-slotted~="ui-combobox"]))) {
|
|
788
|
+
max-block-size: none;
|
|
779
789
|
flex: 1 0 var(--_ui-combobox-item-input-min-inline-size, 5rem);
|
|
780
790
|
width: auto;
|
|
781
791
|
min-block-size: 1.75rem;
|
package/styles/ui-dialog.css
CHANGED
|
@@ -193,58 +193,10 @@
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
.close {
|
|
196
|
-
display:
|
|
197
|
-
|
|
198
|
-
flex: none;
|
|
196
|
+
display: flex !important;
|
|
197
|
+
align-items: center !important;
|
|
198
|
+
flex: none !important;
|
|
199
199
|
margin-inline-start: auto !important;
|
|
200
|
-
inline-size: var(--ui-control-size-sm);
|
|
201
|
-
block-size: var(--ui-control-size-sm);
|
|
202
|
-
padding: 0 !important;
|
|
203
|
-
border: 0;
|
|
204
|
-
border-radius: var(--ui-radius-round);
|
|
205
|
-
background: transparent;
|
|
206
|
-
color: var(--ui-text-secondary);
|
|
207
|
-
cursor: pointer;
|
|
208
|
-
transition: background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/* A CSS-drawn X. */
|
|
212
|
-
.close:where(:not([data-component], [data-slotted]))::before,
|
|
213
|
-
.close:where(:not([data-component], [data-slotted]))::after {
|
|
214
|
-
content: "";
|
|
215
|
-
grid-area: 1 / 1;
|
|
216
|
-
inline-size: 0.875rem;
|
|
217
|
-
block-size: 1.5px;
|
|
218
|
-
border-radius: 1px;
|
|
219
|
-
background: currentColor;
|
|
220
|
-
transform: rotate(45deg);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.close:where(:not([data-component], [data-slotted]))::after {
|
|
224
|
-
transform: rotate(-45deg);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.close:hover {
|
|
228
|
-
background: var(--ui-surface-muted);
|
|
229
|
-
color: var(--ui-text-primary);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.close:focus-visible {
|
|
233
|
-
outline: var(--ui-focus-width) solid var(--ui-focus-ring);
|
|
234
|
-
outline-offset: var(--ui-focus-width);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/* A touch target, like Button on coarse pointers. */
|
|
238
|
-
@media (pointer: coarse) {
|
|
239
|
-
.close {
|
|
240
|
-
inline-size: var(--ui-control-min-block-size);
|
|
241
|
-
block-size: var(--ui-control-min-block-size);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
html[data-ui-input-modality="touch"] :scope:where([data-component~="ui-dialog"]) .close {
|
|
246
|
-
inline-size: var(--ui-control-min-block-size);
|
|
247
|
-
block-size: var(--ui-control-min-block-size);
|
|
248
200
|
}
|
|
249
201
|
|
|
250
202
|
.body {
|
|
@@ -320,12 +272,6 @@
|
|
|
320
272
|
/* Alerts have no close target to add height above the title. */
|
|
321
273
|
|
|
322
274
|
/* :host-qualified so page heading rules that reach in cannot outrank the dialog chrome. */
|
|
323
|
-
|
|
324
|
-
/* A CSS-drawn X. */
|
|
325
|
-
|
|
326
|
-
/* A touch target, like Button on coarse pointers. */
|
|
327
|
-
@media (pointer: coarse) {
|
|
328
|
-
}
|
|
329
275
|
}
|
|
330
276
|
@scope ([data-slotted~="ui-dialog"]:not([data-component])) to ([data-component], [data-slotted]) {
|
|
331
277
|
/* This component's hooks style the element they are set on, and never a nested one. */
|
|
@@ -514,57 +460,10 @@
|
|
|
514
460
|
}
|
|
515
461
|
|
|
516
462
|
.close:where(:scope, :scope *) {
|
|
517
|
-
display:
|
|
518
|
-
|
|
519
|
-
flex: none;
|
|
463
|
+
display: flex !important;
|
|
464
|
+
align-items: center !important;
|
|
465
|
+
flex: none !important;
|
|
520
466
|
margin-inline-start: auto !important;
|
|
521
|
-
inline-size: var(--ui-control-size-sm);
|
|
522
|
-
block-size: var(--ui-control-size-sm);
|
|
523
|
-
padding: 0 !important;
|
|
524
|
-
border: 0;
|
|
525
|
-
border-radius: var(--ui-radius-round);
|
|
526
|
-
background: transparent;
|
|
527
|
-
color: var(--ui-text-secondary);
|
|
528
|
-
cursor: pointer;
|
|
529
|
-
transition: background-color var(--ui-motion-fast) var(--ui-motion-ease), color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/* A CSS-drawn X. */
|
|
533
|
-
.close:where(:not([data-component], [data-slotted]:not([data-slotted~="ui-dialog"]))):where(:scope, :scope *)::before, .close:where(:not([data-component], [data-slotted]:not([data-slotted~="ui-dialog"]))):where(:scope, :scope *)::after {
|
|
534
|
-
content: "";
|
|
535
|
-
grid-area: 1 / 1;
|
|
536
|
-
inline-size: 0.875rem;
|
|
537
|
-
block-size: 1.5px;
|
|
538
|
-
border-radius: 1px;
|
|
539
|
-
background: currentColor;
|
|
540
|
-
transform: rotate(45deg);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
.close:where(:not([data-component], [data-slotted]:not([data-slotted~="ui-dialog"]))):where(:scope, :scope *)::after {
|
|
544
|
-
transform: rotate(-45deg);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
.close:hover:where(:scope, :scope *) {
|
|
548
|
-
background: var(--ui-surface-muted);
|
|
549
|
-
color: var(--ui-text-primary);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
.close:focus-visible:where(:scope, :scope *) {
|
|
553
|
-
outline: var(--ui-focus-width) solid var(--ui-focus-ring);
|
|
554
|
-
outline-offset: var(--ui-focus-width);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/* A touch target, like Button on coarse pointers. */
|
|
558
|
-
@media (pointer: coarse) {
|
|
559
|
-
.close:where(:scope, :scope *) {
|
|
560
|
-
inline-size: var(--ui-control-min-block-size);
|
|
561
|
-
block-size: var(--ui-control-min-block-size);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
html[data-ui-input-modality="touch"] :where([data-component~="ui-dialog"]) .close:where(:scope, :not([data-component], [data-slotted]:not([data-slotted~="ui-dialog"]))) {
|
|
566
|
-
inline-size: var(--ui-control-min-block-size);
|
|
567
|
-
block-size: var(--ui-control-min-block-size);
|
|
568
467
|
}
|
|
569
468
|
|
|
570
469
|
.body:where(:scope, :scope *) {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
Hooks do not inherit, so these are defaults for the input; its own hooks still win. */
|
|
33
33
|
--_ui-default-input-border-width: 0;
|
|
34
34
|
--_ui-default-input-min-block-size: 0;
|
|
35
|
+
--_ui-default-input-frame-width: calc(2 * var(--ui-border-width));
|
|
35
36
|
--_ui-default-input-border: transparent;
|
|
36
37
|
--_ui-default-input-border-hover: transparent;
|
|
37
38
|
--_ui-default-input-focus-border: transparent;
|
package/styles/ui-input.css
CHANGED
|
@@ -61,15 +61,24 @@
|
|
|
61
61
|
syntax: "*";
|
|
62
62
|
inherits: false;
|
|
63
63
|
}
|
|
64
|
+
/* Normalize a unitless zero border to a length before the box calculation. */
|
|
65
|
+
@property --_ui-input-stroke {
|
|
66
|
+
syntax: "<length>";
|
|
67
|
+
inherits: false;
|
|
68
|
+
initial-value: 1px;
|
|
69
|
+
}
|
|
64
70
|
|
|
65
71
|
:scope:where([data-component~="ui-input"]) {
|
|
72
|
+
--_ui-input-size: var(--ui-control-size-md);
|
|
73
|
+
--_ui-input-inset-block: var(--ui-control-inset-block-md, var(--ui-space-2));
|
|
74
|
+
--_ui-input-stroke: var(--ui-input-border-width, var(--_ui-default-input-border-width, var(--ui-border-width)));
|
|
66
75
|
box-sizing: border-box !important;
|
|
67
76
|
display: inline-block !important;
|
|
68
77
|
inline-size: var(--ui-input-inline-size, 100%);
|
|
69
78
|
min-inline-size: 0;
|
|
70
79
|
min-block-size: var(--ui-input-min-block-size, var(--_ui-default-input-min-block-size, var(--ui-control-size-md)));
|
|
71
80
|
margin: 0 !important;
|
|
72
|
-
padding: var(--
|
|
81
|
+
padding: var(--_ui-input-inset-block) var(--ui-space-3) !important;
|
|
73
82
|
appearance: none;
|
|
74
83
|
padding-inline-start: var(--_ui-default-input-padding-inline-start, var(--ui-space-3)) !important;
|
|
75
84
|
border: var(--ui-input-border-width, var(--_ui-default-input-border-width, var(--ui-border-width))) solid
|
|
@@ -87,16 +96,25 @@
|
|
|
87
96
|
background-color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
88
97
|
}
|
|
89
98
|
|
|
99
|
+
/* Cap Safari's excess text metrics without changing native date, file, color or range widgets. */
|
|
100
|
+
:scope:where([data-component~="ui-input"]):is([type="text"], [type="search"], [type="tel"], [type="url"], [type="email"], [type="password"], [type="number"]) {
|
|
101
|
+
max-block-size: max(calc(var(--_ui-input-size) - var(--_ui-default-input-frame-width, 0px)), calc(1em * var(--ui-line-height-tight) + 2 * var(--_ui-input-inset-block) + 2 * var(--_ui-input-stroke)));
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
/* Each size reads the hook and then an Input Group's default first, so a group, which draws the frame, can zero it. */
|
|
91
105
|
:scope:where([data-component~="ui-input"])[data-ui-input-state~="size=sm"] {
|
|
106
|
+
--_ui-input-size: var(--ui-control-size-sm);
|
|
107
|
+
--_ui-input-inset-block: var(--ui-control-inset-block-sm, var(--ui-space-1));
|
|
92
108
|
min-block-size: var(--ui-input-min-block-size, var(--_ui-default-input-min-block-size, var(--ui-control-size-sm)));
|
|
93
|
-
padding: var(--
|
|
109
|
+
padding: var(--_ui-input-inset-block) var(--ui-space-2) !important;
|
|
94
110
|
font-size: var(--ui-font-size-sm);
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
:scope:where([data-component~="ui-input"])[data-ui-input-state~="size=lg"] {
|
|
114
|
+
--_ui-input-size: var(--ui-control-size-lg);
|
|
115
|
+
--_ui-input-inset-block: var(--ui-control-inset-block-lg, var(--ui-space-3));
|
|
98
116
|
min-block-size: var(--ui-input-min-block-size, var(--_ui-default-input-min-block-size, var(--ui-control-size-lg)));
|
|
99
|
-
padding: var(--
|
|
117
|
+
padding: var(--_ui-input-inset-block) var(--ui-space-4) !important;
|
|
100
118
|
font-size: var(--ui-font-size-md);
|
|
101
119
|
}
|
|
102
120
|
|
|
@@ -177,6 +195,9 @@
|
|
|
177
195
|
@scope ([data-component~="ui-input"]) to ([data-component]) {
|
|
178
196
|
/* This component's hooks style the element they are set on, and never a nested one. An Input
|
|
179
197
|
Group sets --_ui-default-input-* for the input inside it, below the input's own hooks. */
|
|
198
|
+
/* Normalize a unitless zero border to a length before the box calculation. */
|
|
199
|
+
|
|
200
|
+
/* Cap Safari's excess text metrics without changing native date, file, color or range widgets. */
|
|
180
201
|
|
|
181
202
|
/* Each size reads the hook and then an Input Group's default first, so a group, which draws the frame, can zero it. */
|
|
182
203
|
|