@operato/styles 10.29.1 → 10.29.12
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/CHANGELOG.md +18 -0
- package/dist/src/button-styles.d.ts +25 -0
- package/dist/src/button-styles.js +127 -0
- package/dist/src/button-styles.js.map +1 -0
- package/dist/src/form-field-styles.js +39 -0
- package/dist/src/form-field-styles.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/page-frame-styles.d.ts +0 -30
- package/dist/src/page-frame-styles.js +105 -7
- package/dist/src/page-frame-styles.js.map +1 -1
- package/dist/test/page-ground.test.d.ts +1 -0
- package/dist/test/page-ground.test.js +215 -0
- package/dist/test/page-ground.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -2
- package/test/page-ground.test.ts +234 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { expect, fixture, html } from '@open-wc/testing';
|
|
2
|
+
import { css, LitElement, unsafeCSS } from 'lit';
|
|
3
|
+
import { ButtonContainerStyles } from '../src/button-container-styles.js';
|
|
4
|
+
import { ButtonStyles } from '../src/button-styles.js';
|
|
5
|
+
import { FormFieldStyles } from '../src/form-field-styles.js';
|
|
6
|
+
import { DocumentPageStyles, WorkSurfacePageStyles } from '../src/page-frame-styles.js';
|
|
7
|
+
/*
|
|
8
|
+
* The ground the page frames lay, and the button and field sizes (operato-application/design/ui/page-standard.md).
|
|
9
|
+
*
|
|
10
|
+
* What these stand on: a screen that forgot to set a value used to fall to the browser default -
|
|
11
|
+
* 16px body text, rgb(0 0 238) links, Korean broken mid-word, cards wider than their header. The
|
|
12
|
+
* frame now lays the value; a screen that says otherwise still wins. Measured on rendered elements,
|
|
13
|
+
* not on the stylesheet text.
|
|
14
|
+
*
|
|
15
|
+
* The tokens are defined here because a test page has no shell theme.
|
|
16
|
+
*/
|
|
17
|
+
const TOKENS = css `
|
|
18
|
+
:host {
|
|
19
|
+
--ui-t-body: 13px;
|
|
20
|
+
--ui-ink: rgb(27, 27, 27);
|
|
21
|
+
--ui-accent: rgb(0, 114, 126);
|
|
22
|
+
--ui-gutter: 16px;
|
|
23
|
+
--context-action-height: 32px;
|
|
24
|
+
--cell-action-height: 26px;
|
|
25
|
+
--form-element-height-medium: 32px;
|
|
26
|
+
--form-element-height-small: 26px;
|
|
27
|
+
--md-sys-color-primary: rgb(0, 114, 126);
|
|
28
|
+
--md-sys-color-surface-container-lowest: rgb(255, 255, 255);
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
class DocumentProbe extends LitElement {
|
|
32
|
+
render() {
|
|
33
|
+
return html `
|
|
34
|
+
<div class="page">
|
|
35
|
+
<section class="page-section">
|
|
36
|
+
<p class="page-section-note">note</p>
|
|
37
|
+
<div class="page-card" id="card" style="width: 300px; padding: 16px">card</div>
|
|
38
|
+
<span id="text">text</span>
|
|
39
|
+
<a id="thing" href="#a">LOT-1</a>
|
|
40
|
+
<a id="go" class="page-nav-link" href="#b">go</a>
|
|
41
|
+
<div style="display: grid; grid-template-columns: 400px">
|
|
42
|
+
<button id="plain">add</button>
|
|
43
|
+
</div>
|
|
44
|
+
<button id="raised" raised>save</button>
|
|
45
|
+
<button id="small" small>hold</button>
|
|
46
|
+
<input id="field" />
|
|
47
|
+
<input id="small-field" small />
|
|
48
|
+
<textarea id="area"></textarea>
|
|
49
|
+
</section>
|
|
50
|
+
</div>
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
DocumentProbe.styles = [DocumentPageStyles, ButtonStyles, FormFieldStyles, TOKENS];
|
|
55
|
+
customElements.define('document-probe', DocumentProbe);
|
|
56
|
+
class SurfaceProbe extends LitElement {
|
|
57
|
+
render() {
|
|
58
|
+
return html `<div class="page-headroom"><span>range</span></div>`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
SurfaceProbe.styles = [WorkSurfacePageStyles, TOKENS];
|
|
62
|
+
customElements.define('surface-probe', SurfaceProbe);
|
|
63
|
+
class OwnRuleProbe extends LitElement {
|
|
64
|
+
render() {
|
|
65
|
+
return html `<span id="text">t</span><button id="own">x</button><a id="a" href="#">a</a>`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
OwnRuleProbe.styles = [
|
|
69
|
+
DocumentPageStyles,
|
|
70
|
+
ButtonStyles,
|
|
71
|
+
TOKENS,
|
|
72
|
+
css `
|
|
73
|
+
:host {
|
|
74
|
+
font-size: 15px;
|
|
75
|
+
}
|
|
76
|
+
button {
|
|
77
|
+
height: 48px;
|
|
78
|
+
}
|
|
79
|
+
a {
|
|
80
|
+
color: rgb(255, 0, 0);
|
|
81
|
+
}
|
|
82
|
+
`
|
|
83
|
+
];
|
|
84
|
+
customElements.define('own-rule-probe', OwnRuleProbe);
|
|
85
|
+
const style = (el) => getComputedStyle(el);
|
|
86
|
+
describe('page frame ground', () => {
|
|
87
|
+
it('body text is the body token, not the browser 16px', async () => {
|
|
88
|
+
const el = (await fixture(html `<document-probe></document-probe>`));
|
|
89
|
+
expect(style(el.shadowRoot.querySelector('#text')).fontSize).to.equal('13px');
|
|
90
|
+
});
|
|
91
|
+
it('a link to a thing reads as text; a link to a screen takes the accent; neither underlines at rest', async () => {
|
|
92
|
+
const el = (await fixture(html `<document-probe></document-probe>`));
|
|
93
|
+
const thing = style(el.shadowRoot.querySelector('#thing'));
|
|
94
|
+
const go = style(el.shadowRoot.querySelector('#go'));
|
|
95
|
+
expect(thing.color).to.equal('rgb(27, 27, 27)');
|
|
96
|
+
expect(thing.textDecorationLine).to.equal('none');
|
|
97
|
+
expect(go.color).to.equal('rgb(0, 114, 126)');
|
|
98
|
+
});
|
|
99
|
+
it('Korean wraps at words, and a word with no break point still breaks', async () => {
|
|
100
|
+
const el = (await fixture(html `<document-probe></document-probe>`));
|
|
101
|
+
const text = style(el.shadowRoot.querySelector('#text'));
|
|
102
|
+
expect(text.wordBreak).to.equal('keep-all');
|
|
103
|
+
expect(text.overflowWrap).to.equal('anywhere');
|
|
104
|
+
});
|
|
105
|
+
it('padding stays inside the width (a card is not wider than it was told)', async () => {
|
|
106
|
+
const el = (await fixture(html `<document-probe></document-probe>`));
|
|
107
|
+
expect(el.shadowRoot.querySelector('#card').getBoundingClientRect().width).to.equal(300);
|
|
108
|
+
});
|
|
109
|
+
it('the document column is not held to 1080px, and its side gutters are equal', async () => {
|
|
110
|
+
const el = (await fixture(html `<document-probe style="display: block; width: 1600px"></document-probe>`));
|
|
111
|
+
const page = el.shadowRoot.querySelector('.page');
|
|
112
|
+
expect(style(page).maxWidth).to.equal('none');
|
|
113
|
+
expect(page.getBoundingClientRect().width).to.equal(1600);
|
|
114
|
+
expect(style(page).paddingLeft).to.equal('16px');
|
|
115
|
+
expect(style(page).paddingRight).to.equal('16px');
|
|
116
|
+
});
|
|
117
|
+
it('the work surface headroom uses the same gutter', async () => {
|
|
118
|
+
const el = (await fixture(html `<surface-probe></surface-probe>`));
|
|
119
|
+
expect(style(el.shadowRoot.querySelector('.page-headroom')).paddingLeft).to.equal('16px');
|
|
120
|
+
});
|
|
121
|
+
it("a screen's own rule still wins over the ground", async () => {
|
|
122
|
+
const el = (await fixture(html `<own-rule-probe></own-rule-probe>`));
|
|
123
|
+
expect(style(el.shadowRoot.querySelector('#text')).fontSize).to.equal('15px');
|
|
124
|
+
expect(style(el.shadowRoot.querySelector('#own')).height).to.equal('48px');
|
|
125
|
+
expect(style(el.shadowRoot.querySelector('#a')).color).to.equal('rgb(255, 0, 0)');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('buttons and fields', () => {
|
|
129
|
+
it('a button is as tall as its place and as wide as its label - it does not stretch to a grid track', async () => {
|
|
130
|
+
const el = (await fixture(html `<document-probe></document-probe>`));
|
|
131
|
+
const plain = el.shadowRoot.querySelector('#plain');
|
|
132
|
+
expect(style(plain).height).to.equal('32px');
|
|
133
|
+
expect(style(plain).fontSize).to.equal('13px');
|
|
134
|
+
expect(style(plain).fontWeight).to.equal('600');
|
|
135
|
+
expect(plain.getBoundingClientRect().width).to.be.below(100);
|
|
136
|
+
expect(style(el.shadowRoot.querySelector('#small')).height).to.equal('26px');
|
|
137
|
+
});
|
|
138
|
+
it('a raised button is filled, a plain one is not', async () => {
|
|
139
|
+
const el = (await fixture(html `<document-probe></document-probe>`));
|
|
140
|
+
const raised = style(el.shadowRoot.querySelector('#raised')).backgroundColor;
|
|
141
|
+
const plain = style(el.shadowRoot.querySelector('#plain')).backgroundColor;
|
|
142
|
+
expect(raised).to.not.equal(plain);
|
|
143
|
+
});
|
|
144
|
+
it('one-line fields take the form height; a small one the cell height; a textarea keeps its own', async () => {
|
|
145
|
+
const el = (await fixture(html `<document-probe></document-probe>`));
|
|
146
|
+
expect(style(el.shadowRoot.querySelector('#field')).height).to.equal('32px');
|
|
147
|
+
expect(style(el.shadowRoot.querySelector('#small-field')).height).to.equal('26px');
|
|
148
|
+
expect(style(el.shadowRoot.querySelector('#area')).height).to.not.equal('32px');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
/*
|
|
152
|
+
* The touch branch of FormFieldStyles, measured as drawn. The test browser reports a fine pointer, so the
|
|
153
|
+
* rules inside the pointer: coarse media block are lifted out of the published CSS and applied as they are.
|
|
154
|
+
*/
|
|
155
|
+
const TOUCH_RULES = (() => {
|
|
156
|
+
const text = FormFieldStyles.cssText;
|
|
157
|
+
const start = text.indexOf('@media (pointer: coarse)');
|
|
158
|
+
const open = text.indexOf('{', start);
|
|
159
|
+
let depth = 1;
|
|
160
|
+
let i = open + 1;
|
|
161
|
+
for (; i < text.length && depth; i++)
|
|
162
|
+
depth += text[i] === '{' ? 1 : text[i] === '}' ? -1 : 0;
|
|
163
|
+
return start < 0 ? '' : text.slice(open + 1, i - 1);
|
|
164
|
+
})();
|
|
165
|
+
class TouchProbe extends LitElement {
|
|
166
|
+
render() {
|
|
167
|
+
return html `
|
|
168
|
+
<label id="label">name <input id="field" value="Ag gj" /></label>
|
|
169
|
+
<input id="small-field" small value="Ag gj" />
|
|
170
|
+
<select id="pick"><option>Ag gj</option></select>
|
|
171
|
+
<textarea id="area">Ag gj</textarea>
|
|
172
|
+
<span id="help">help</span>
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
TouchProbe.styles = [DocumentPageStyles, FormFieldStyles, TOKENS, unsafeCSS(TOUCH_RULES)];
|
|
177
|
+
customElements.define('touch-probe', TouchProbe);
|
|
178
|
+
describe('fields on a touch device', () => {
|
|
179
|
+
it('the branch exists in the published CSS', () => {
|
|
180
|
+
expect(TOUCH_RULES).to.contain('16px');
|
|
181
|
+
});
|
|
182
|
+
it('field text is 16px - iOS zooms into anything smaller - while labels and help stay at body size', async () => {
|
|
183
|
+
const el = (await fixture(html `<touch-probe></touch-probe>`));
|
|
184
|
+
const at = (id) => style(el.shadowRoot.querySelector(id));
|
|
185
|
+
for (const id of ['#field', '#small-field', '#pick', '#area'])
|
|
186
|
+
expect(at(id).fontSize, id).to.equal('16px');
|
|
187
|
+
expect(at('#label').fontSize).to.equal('13px');
|
|
188
|
+
expect(at('#help').fontSize).to.equal('13px');
|
|
189
|
+
});
|
|
190
|
+
it('the field heights stay, and 16px text fits inside them', async () => {
|
|
191
|
+
const el = (await fixture(html `<touch-probe></touch-probe>`));
|
|
192
|
+
for (const [id, height] of [['#field', 32], ['#small-field', 26]]) {
|
|
193
|
+
const field = el.shadowRoot.querySelector(id);
|
|
194
|
+
const s = style(field);
|
|
195
|
+
expect(s.height, id).to.equal(`${height}px`);
|
|
196
|
+
const inner = height - parseFloat(s.paddingTop) - parseFloat(s.paddingBottom) - parseFloat(s.borderTopWidth) - parseFloat(s.borderBottomWidth);
|
|
197
|
+
/* 16px text needs about 1.2 of its size; with less room the bottom of the letters is cut. */
|
|
198
|
+
expect(inner, id).to.be.at.least(16 * 1.2);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
describe('ButtonContainerStyles', () => {
|
|
203
|
+
/*
|
|
204
|
+
* dssp draws 26 of its own pages with this block. ButtonStyles was taken out next to it, not out of
|
|
205
|
+
* it: the CSS this block emits must stay byte for byte what 10.29.1 published.
|
|
206
|
+
* sha-256 of ButtonContainerStyles.cssText in the published 10.29.1 (2026-10-05).
|
|
207
|
+
*/
|
|
208
|
+
it('emits the same CSS as the published 10.29.1', async () => {
|
|
209
|
+
const bytes = new TextEncoder().encode(ButtonContainerStyles.cssText);
|
|
210
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
|
211
|
+
const hex = [...new Uint8Array(digest)].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
212
|
+
expect(hex).to.equal('bcab002afc1a4521cd7da312cacba2bcd594cfe0e3dcd6c1d62a388adfd25203');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
//# sourceMappingURL=page-ground.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-ground.test.js","sourceRoot":"","sources":["../../test/page-ground.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, html } from '@open-wc/testing'\nimport { css, LitElement, unsafeCSS } from 'lit'\n\nimport { ButtonContainerStyles } from '../src/button-container-styles.js'\nimport { ButtonStyles } from '../src/button-styles.js'\nimport { FormFieldStyles } from '../src/form-field-styles.js'\nimport { DocumentPageStyles, WorkSurfacePageStyles } from '../src/page-frame-styles.js'\n\n/*\n * The ground the page frames lay, and the button and field sizes (operato-application/design/ui/page-standard.md).\n *\n * What these stand on: a screen that forgot to set a value used to fall to the browser default -\n * 16px body text, rgb(0 0 238) links, Korean broken mid-word, cards wider than their header. The\n * frame now lays the value; a screen that says otherwise still wins. Measured on rendered elements,\n * not on the stylesheet text.\n *\n * The tokens are defined here because a test page has no shell theme.\n */\nconst TOKENS = css`\n :host {\n --ui-t-body: 13px;\n --ui-ink: rgb(27, 27, 27);\n --ui-accent: rgb(0, 114, 126);\n --ui-gutter: 16px;\n --context-action-height: 32px;\n --cell-action-height: 26px;\n --form-element-height-medium: 32px;\n --form-element-height-small: 26px;\n --md-sys-color-primary: rgb(0, 114, 126);\n --md-sys-color-surface-container-lowest: rgb(255, 255, 255);\n }\n`\n\nclass DocumentProbe extends LitElement {\n static styles = [DocumentPageStyles, ButtonStyles, FormFieldStyles, TOKENS]\n render() {\n return html`\n <div class=\"page\">\n <section class=\"page-section\">\n <p class=\"page-section-note\">note</p>\n <div class=\"page-card\" id=\"card\" style=\"width: 300px; padding: 16px\">card</div>\n <span id=\"text\">text</span>\n <a id=\"thing\" href=\"#a\">LOT-1</a>\n <a id=\"go\" class=\"page-nav-link\" href=\"#b\">go</a>\n <div style=\"display: grid; grid-template-columns: 400px\">\n <button id=\"plain\">add</button>\n </div>\n <button id=\"raised\" raised>save</button>\n <button id=\"small\" small>hold</button>\n <input id=\"field\" />\n <input id=\"small-field\" small />\n <textarea id=\"area\"></textarea>\n </section>\n </div>\n `\n }\n}\ncustomElements.define('document-probe', DocumentProbe)\n\nclass SurfaceProbe extends LitElement {\n static styles = [WorkSurfacePageStyles, TOKENS]\n render() {\n return html`<div class=\"page-headroom\"><span>range</span></div>`\n }\n}\ncustomElements.define('surface-probe', SurfaceProbe)\n\nclass OwnRuleProbe extends LitElement {\n static styles = [\n DocumentPageStyles,\n ButtonStyles,\n TOKENS,\n css`\n :host {\n font-size: 15px;\n }\n button {\n height: 48px;\n }\n a {\n color: rgb(255, 0, 0);\n }\n `\n ]\n render() {\n return html`<span id=\"text\">t</span><button id=\"own\">x</button><a id=\"a\" href=\"#\">a</a>`\n }\n}\ncustomElements.define('own-rule-probe', OwnRuleProbe)\n\nconst style = (el: Element) => getComputedStyle(el)\n\ndescribe('page frame ground', () => {\n it('body text is the body token, not the browser 16px', async () => {\n const el = (await fixture(html`<document-probe></document-probe>`)) as DocumentProbe\n expect(style(el.shadowRoot!.querySelector('#text')!).fontSize).to.equal('13px')\n })\n\n it('a link to a thing reads as text; a link to a screen takes the accent; neither underlines at rest', async () => {\n const el = (await fixture(html`<document-probe></document-probe>`)) as DocumentProbe\n const thing = style(el.shadowRoot!.querySelector('#thing')!)\n const go = style(el.shadowRoot!.querySelector('#go')!)\n expect(thing.color).to.equal('rgb(27, 27, 27)')\n expect(thing.textDecorationLine).to.equal('none')\n expect(go.color).to.equal('rgb(0, 114, 126)')\n })\n\n it('Korean wraps at words, and a word with no break point still breaks', async () => {\n const el = (await fixture(html`<document-probe></document-probe>`)) as DocumentProbe\n const text = style(el.shadowRoot!.querySelector('#text')!)\n expect(text.wordBreak).to.equal('keep-all')\n expect(text.overflowWrap).to.equal('anywhere')\n })\n\n it('padding stays inside the width (a card is not wider than it was told)', async () => {\n const el = (await fixture(html`<document-probe></document-probe>`)) as DocumentProbe\n expect(el.shadowRoot!.querySelector('#card')!.getBoundingClientRect().width).to.equal(300)\n })\n\n it('the document column is not held to 1080px, and its side gutters are equal', async () => {\n const el = (await fixture(html`<document-probe style=\"display: block; width: 1600px\"></document-probe>`)) as DocumentProbe\n const page = el.shadowRoot!.querySelector('.page')!\n expect(style(page).maxWidth).to.equal('none')\n expect(page.getBoundingClientRect().width).to.equal(1600)\n expect(style(page).paddingLeft).to.equal('16px')\n expect(style(page).paddingRight).to.equal('16px')\n })\n\n it('the work surface headroom uses the same gutter', async () => {\n const el = (await fixture(html`<surface-probe></surface-probe>`)) as SurfaceProbe\n expect(style(el.shadowRoot!.querySelector('.page-headroom')!).paddingLeft).to.equal('16px')\n })\n\n it(\"a screen's own rule still wins over the ground\", async () => {\n const el = (await fixture(html`<own-rule-probe></own-rule-probe>`)) as OwnRuleProbe\n expect(style(el.shadowRoot!.querySelector('#text')!).fontSize).to.equal('15px')\n expect(style(el.shadowRoot!.querySelector('#own')!).height).to.equal('48px')\n expect(style(el.shadowRoot!.querySelector('#a')!).color).to.equal('rgb(255, 0, 0)')\n })\n})\n\ndescribe('buttons and fields', () => {\n it('a button is as tall as its place and as wide as its label - it does not stretch to a grid track', async () => {\n const el = (await fixture(html`<document-probe></document-probe>`)) as DocumentProbe\n const plain = el.shadowRoot!.querySelector('#plain')!\n expect(style(plain).height).to.equal('32px')\n expect(style(plain).fontSize).to.equal('13px')\n expect(style(plain).fontWeight).to.equal('600')\n expect(plain.getBoundingClientRect().width).to.be.below(100)\n expect(style(el.shadowRoot!.querySelector('#small')!).height).to.equal('26px')\n })\n\n it('a raised button is filled, a plain one is not', async () => {\n const el = (await fixture(html`<document-probe></document-probe>`)) as DocumentProbe\n const raised = style(el.shadowRoot!.querySelector('#raised')!).backgroundColor\n const plain = style(el.shadowRoot!.querySelector('#plain')!).backgroundColor\n expect(raised).to.not.equal(plain)\n })\n\n it('one-line fields take the form height; a small one the cell height; a textarea keeps its own', async () => {\n const el = (await fixture(html`<document-probe></document-probe>`)) as DocumentProbe\n expect(style(el.shadowRoot!.querySelector('#field')!).height).to.equal('32px')\n expect(style(el.shadowRoot!.querySelector('#small-field')!).height).to.equal('26px')\n expect(style(el.shadowRoot!.querySelector('#area')!).height).to.not.equal('32px')\n })\n})\n\n/*\n * The touch branch of FormFieldStyles, measured as drawn. The test browser reports a fine pointer, so the\n * rules inside the pointer: coarse media block are lifted out of the published CSS and applied as they are.\n */\nconst TOUCH_RULES = (() => {\n const text = FormFieldStyles.cssText\n const start = text.indexOf('@media (pointer: coarse)')\n const open = text.indexOf('{', start)\n let depth = 1\n let i = open + 1\n for (; i < text.length && depth; i++) depth += text[i] === '{' ? 1 : text[i] === '}' ? -1 : 0\n return start < 0 ? '' : text.slice(open + 1, i - 1)\n})()\n\nclass TouchProbe extends LitElement {\n static styles = [DocumentPageStyles, FormFieldStyles, TOKENS, unsafeCSS(TOUCH_RULES)]\n render() {\n return html`\n <label id=\"label\">name <input id=\"field\" value=\"Ag gj\" /></label>\n <input id=\"small-field\" small value=\"Ag gj\" />\n <select id=\"pick\"><option>Ag gj</option></select>\n <textarea id=\"area\">Ag gj</textarea>\n <span id=\"help\">help</span>\n `\n }\n}\ncustomElements.define('touch-probe', TouchProbe)\n\ndescribe('fields on a touch device', () => {\n it('the branch exists in the published CSS', () => {\n expect(TOUCH_RULES).to.contain('16px')\n })\n\n it('field text is 16px - iOS zooms into anything smaller - while labels and help stay at body size', async () => {\n const el = (await fixture(html`<touch-probe></touch-probe>`)) as TouchProbe\n const at = (id: string) => style(el.shadowRoot!.querySelector(id)!)\n for (const id of ['#field', '#small-field', '#pick', '#area']) expect(at(id).fontSize, id).to.equal('16px')\n expect(at('#label').fontSize).to.equal('13px')\n expect(at('#help').fontSize).to.equal('13px')\n })\n\n it('the field heights stay, and 16px text fits inside them', async () => {\n const el = (await fixture(html`<touch-probe></touch-probe>`)) as TouchProbe\n for (const [id, height] of [['#field', 32], ['#small-field', 26]] as const) {\n const field = el.shadowRoot!.querySelector(id) as HTMLInputElement\n const s = style(field)\n expect(s.height, id).to.equal(`${height}px`)\n const inner = height - parseFloat(s.paddingTop) - parseFloat(s.paddingBottom) - parseFloat(s.borderTopWidth) - parseFloat(s.borderBottomWidth)\n /* 16px text needs about 1.2 of its size; with less room the bottom of the letters is cut. */\n expect(inner, id).to.be.at.least(16 * 1.2)\n }\n })\n})\n\ndescribe('ButtonContainerStyles', () => {\n /*\n * dssp draws 26 of its own pages with this block. ButtonStyles was taken out next to it, not out of\n * it: the CSS this block emits must stay byte for byte what 10.29.1 published.\n * sha-256 of ButtonContainerStyles.cssText in the published 10.29.1 (2026-10-05).\n */\n it('emits the same CSS as the published 10.29.1', async () => {\n const bytes = new TextEncoder().encode(ButtonContainerStyles.cssText)\n const digest = await crypto.subtle.digest('SHA-256', bytes)\n const hex = [...new Uint8Array(digest)].map(b => b.toString(16).padStart(2, '0')).join('')\n expect(hex).to.equal('bcab002afc1a4521cd7da312cacba2bcd594cfe0e3dcd6c1d62a388adfd25203')\n })\n})\n"]}
|