vintage-frames 0.12.3 → 0.12.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/README.md +1 -1
- package/custom-elements.json +116 -6
- package/dist/components/vf-button.d.ts +53 -4
- package/dist/components/vf-button.js +65 -17
- package/dist/text-control.js +1 -1
- package/docs/SPEC.md +5 -4
- package/editor/vscode.html-custom-data.json +21 -1
- package/editor/web-types.json +42 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A web component kit that rebuilds the classic Apple System 7 interface elements.
|
|
4
4
|
|
|
5
|
-
View the [Examples Page](https://
|
|
5
|
+
View the [Examples Page](https://vintage-frames.portill.io/) to see every element live, with its full API.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm install vintage-frames
|
package/custom-elements.json
CHANGED
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
"declarations": [
|
|
234
234
|
{
|
|
235
235
|
"kind": "class",
|
|
236
|
-
"description": "The classic System 7 push button (\"OK\", \"Cancel\", \"Install\", …).\n\nA rounded-rectangle control with a 1px black border and white face that\ninverts to white-on-black while pressed. `variant=\"default\"` draws the\nclassic bold double ring around the button, marking it as the default\naction of a dialog.\n\nThe rounded corners are not `border-radius` arcs: frame, face, and ring are\nstepped `clip-path` silhouettes traced pixel-for-pixel from the kit's button\nreference sheet (see `src/pixel-frame.ts`), so every corner renders as the\nexact 1-bit staircase with no antialiasing.\n\nKeyboard focus — an affordance System 7 didn't have, drawn in its idiom\nanyway — is a 1px dashed rule under the label text (`vfFocusUnderline`)\nrather than a ring around the control.\n\nForm-associated: place it inside a `<form>` and `type=\"submit\"` submits the\nform (contributing its `name`/`value` and any `form*` override to the\nsubmission), `type=\"reset\"` resets it. Enter and Space activate it via the\ninner native button, and the submission runs at the end of the click's\npropagation, so `preventDefault()` on the button cancels it the way it\ncancels a native one.\n\nOne thing the platform will not allow: `event.submitter` cannot BE a\nform-associated custom element. It is the transient native proxy this\nbutton submits through — so read the submitting button as\n`event.submitter.closest('vf-button')`, and its identity from\n`submitter.name`/`.value` rather than by comparing element references.\n\nA host-level `aria-label` / `aria-labelledby` names the inner button, and\n`description` (or a host-level `aria-describedby`) describes it — the same\nbridge the fields use, since the role lives on a shadow-internal node the\nplatform can't deliver those to. A `<label for>` deliberately does not\nname it: a `<button>` is not a labelable element.",
|
|
236
|
+
"description": "The classic System 7 push button (\"OK\", \"Cancel\", \"Install\", …).\n\nA rounded-rectangle control with a 1px black border and white face that\ninverts to white-on-black while pressed. `variant=\"default\"` draws the\nclassic bold double ring around the button, marking it as the default\naction of a dialog.\n\nThe rounded corners are not `border-radius` arcs: frame, face, and ring are\nstepped `clip-path` silhouettes traced pixel-for-pixel from the kit's button\nreference sheet (see `src/pixel-frame.ts`), so every corner renders as the\nexact 1-bit staircase with no antialiasing.\n\nKeyboard focus — an affordance System 7 didn't have, drawn in its idiom\nanyway — is a 1px dashed rule under the label text (`vfFocusUnderline`)\nrather than a ring around the control.\n\nForm-associated: place it inside a `<form>` and `type=\"submit\"` submits the\nform (contributing its `name`/`value` and any `form*` override to the\nsubmission), `type=\"reset\"` resets it. Enter and Space activate it via the\ninner native button, and the submission runs at the end of the click's\npropagation, so `preventDefault()` on the button cancels it the way it\ncancels a native one.\n\nOne thing the platform will not allow: `event.submitter` cannot BE a\nform-associated custom element. It is the transient native proxy this\nbutton submits through — so read the submitting button as\n`event.submitter.closest('vf-button')`, and its identity from\n`submitter.name`/`.value` rather than by comparing element references.\n\nWith an `href` it is a link instead: the same art over an `<a href>` in\nplace of the `<button>`, so it is followed the way any link is — a click,\nEnter, a modified or middle click into a new tab, the context menu's link\nitems — and `target`, `rel` and `download` mean what they mean on `<a>`.\nA link is not a form button: `type`, `name`, `value` and the `form*`\noverrides do nothing while `href` is set.\n\nA host-level `aria-label` / `aria-labelledby` names the inner button, and\n`description` (or a host-level `aria-describedby`) describes it — the same\nbridge the fields use, since the role lives on a shadow-internal node the\nplatform can't deliver those to. A `<label for>` deliberately does not\nname it: a `<button>` is not a labelable element.",
|
|
237
237
|
"name": "VfButton",
|
|
238
238
|
"cssProperties": [
|
|
239
239
|
{
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
],
|
|
245
245
|
"cssParts": [
|
|
246
246
|
{
|
|
247
|
-
"description": "The inner native `<button>` element
|
|
247
|
+
"description": "The inner native `<button>` element — an `<a>` with `href`.",
|
|
248
248
|
"name": "button"
|
|
249
249
|
}
|
|
250
250
|
],
|
|
@@ -362,6 +362,46 @@
|
|
|
362
362
|
"attribute": "formtarget",
|
|
363
363
|
"reflects": true
|
|
364
364
|
},
|
|
365
|
+
{
|
|
366
|
+
"kind": "field",
|
|
367
|
+
"name": "href",
|
|
368
|
+
"type": {
|
|
369
|
+
"text": "string | undefined"
|
|
370
|
+
},
|
|
371
|
+
"description": "A URL, and the button becomes a link to it: an `<a href>` renders in\nplace of the inner `<button>` under the same art, and the platform\nfollows it — a click, Enter, a modified or middle click, the context\nmenu's link items. An empty value is no link.\n\nA link is not a form button. While `href` is set, type,\nname, value and the `form*` overrides do nothing, and the\nbutton is never a form's default button for a field's Enter. Inside a\n`vf-dialog`, `variant=\"default\"` still makes it the dialog's default\nbutton, so Return follows the link.\n\nDisabled — by attribute or an ancestor `<fieldset disabled>` — the `<a>`\nkeeps rendering but loses its `href`, taking `role=\"link\"` and\n`aria-disabled=\"true\"` instead: out of the tab order, and inert to the\npointer and to click, as a disabled `<button>` is.",
|
|
372
|
+
"attribute": "href",
|
|
373
|
+
"reflects": true
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"kind": "field",
|
|
377
|
+
"name": "target",
|
|
378
|
+
"type": {
|
|
379
|
+
"text": "string | undefined"
|
|
380
|
+
},
|
|
381
|
+
"description": "With href: where the link opens, as `<a target>`.",
|
|
382
|
+
"attribute": "target",
|
|
383
|
+
"reflects": true
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"kind": "field",
|
|
387
|
+
"name": "rel",
|
|
388
|
+
"type": {
|
|
389
|
+
"text": "string | undefined"
|
|
390
|
+
},
|
|
391
|
+
"description": "With href: the link's relationship to this page, as `<a rel>`.",
|
|
392
|
+
"attribute": "rel",
|
|
393
|
+
"reflects": true
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"kind": "field",
|
|
397
|
+
"name": "download",
|
|
398
|
+
"type": {
|
|
399
|
+
"text": "string | undefined"
|
|
400
|
+
},
|
|
401
|
+
"description": "With href: download the resource instead of navigating to it, as\n`<a download>` — the value, when there is one, names the saved file.",
|
|
402
|
+
"attribute": "download",
|
|
403
|
+
"reflects": true
|
|
404
|
+
},
|
|
365
405
|
{
|
|
366
406
|
"kind": "field",
|
|
367
407
|
"name": "scale",
|
|
@@ -380,11 +420,22 @@
|
|
|
380
420
|
},
|
|
381
421
|
{
|
|
382
422
|
"kind": "field",
|
|
383
|
-
"name": "
|
|
423
|
+
"name": "control",
|
|
384
424
|
"type": {
|
|
385
|
-
"text": "
|
|
425
|
+
"text": "HTMLElement | null"
|
|
386
426
|
},
|
|
387
|
-
"privacy": "private"
|
|
427
|
+
"privacy": "private",
|
|
428
|
+
"description": "The inner `<button>` — or `<a>`, under href."
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"kind": "field",
|
|
432
|
+
"name": "isLink",
|
|
433
|
+
"type": {
|
|
434
|
+
"text": "boolean"
|
|
435
|
+
},
|
|
436
|
+
"privacy": "private",
|
|
437
|
+
"description": "Whether href makes this a link.",
|
|
438
|
+
"readonly": true
|
|
388
439
|
},
|
|
389
440
|
{
|
|
390
441
|
"kind": "field",
|
|
@@ -414,7 +465,34 @@
|
|
|
414
465
|
"text": "void"
|
|
415
466
|
}
|
|
416
467
|
},
|
|
417
|
-
"description": "Forwards a synthetic activation to the real `<button>` in the shadow\nroot. A `click()` on the host dispatches at the host and propagates *up*,\nnever down to the inner button's listener — so the native control, the\nactual activation surface, is handed the call instead. Its click then\nbubbles back out composed, exactly as a pointer's does. This is what the\nfields' implicit submission (Enter) activates as the form's default\nbutton; a disabled button swallows it natively, as it should.\n\nBefore the first render there is no inner button to forward to. A native\n`click()` always fires, so rather than doing nothing this falls back to\ndispatching at the host — no activation behavior to run yet, but the\nevent a caller asked for."
|
|
468
|
+
"description": "Forwards a synthetic activation to the real `<button>` in the shadow\nroot. A `click()` on the host dispatches at the host and propagates *up*,\nnever down to the inner button's listener — so the native control, the\nactual activation surface, is handed the call instead. Its click then\nbubbles back out composed, exactly as a pointer's does. This is what the\nfields' implicit submission (Enter) activates as the form's default\nbutton; a disabled button swallows it natively, as it should. Under\nhref the `<a>` is handed the call and follows itself — which is\nhow a dialog's Return reaches a default button that is a link. A\ndisabled link has no native state to swallow it, so it is withheld here.\n\nBefore the first render there is no inner button to forward to. A native\n`click()` always fires, so rather than doing nothing this falls back to\ndispatching at the host — no activation behavior to run yet, but the\nevent a caller asked for."
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"kind": "method",
|
|
472
|
+
"name": "renderButton",
|
|
473
|
+
"privacy": "private",
|
|
474
|
+
"parameters": [
|
|
475
|
+
{
|
|
476
|
+
"name": "label",
|
|
477
|
+
"type": {
|
|
478
|
+
"text": "TemplateResult"
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
"kind": "method",
|
|
485
|
+
"name": "renderLink",
|
|
486
|
+
"privacy": "private",
|
|
487
|
+
"parameters": [
|
|
488
|
+
{
|
|
489
|
+
"name": "label",
|
|
490
|
+
"type": {
|
|
491
|
+
"text": "TemplateResult"
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
],
|
|
495
|
+
"description": "The link. No click listener: following it is the platform's activation\nbehavior. A disabled one is an `<a>` without an `href`, which has no\nlink role unless it is given one."
|
|
418
496
|
},
|
|
419
497
|
{
|
|
420
498
|
"kind": "field",
|
|
@@ -1110,6 +1188,38 @@
|
|
|
1110
1188
|
"description": "See formAction. Overrides the form's `target`.",
|
|
1111
1189
|
"fieldName": "formTarget"
|
|
1112
1190
|
},
|
|
1191
|
+
{
|
|
1192
|
+
"name": "href",
|
|
1193
|
+
"type": {
|
|
1194
|
+
"text": "string | undefined"
|
|
1195
|
+
},
|
|
1196
|
+
"description": "A URL, and the button becomes a link to it: an `<a href>` renders in\nplace of the inner `<button>` under the same art, and the platform\nfollows it — a click, Enter, a modified or middle click, the context\nmenu's link items. An empty value is no link.\n\nA link is not a form button. While `href` is set, type,\nname, value and the `form*` overrides do nothing, and the\nbutton is never a form's default button for a field's Enter. Inside a\n`vf-dialog`, `variant=\"default\"` still makes it the dialog's default\nbutton, so Return follows the link.\n\nDisabled — by attribute or an ancestor `<fieldset disabled>` — the `<a>`\nkeeps rendering but loses its `href`, taking `role=\"link\"` and\n`aria-disabled=\"true\"` instead: out of the tab order, and inert to the\npointer and to click, as a disabled `<button>` is.",
|
|
1197
|
+
"fieldName": "href"
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
"name": "target",
|
|
1201
|
+
"type": {
|
|
1202
|
+
"text": "string | undefined"
|
|
1203
|
+
},
|
|
1204
|
+
"description": "With href: where the link opens, as `<a target>`.",
|
|
1205
|
+
"fieldName": "target"
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
"name": "rel",
|
|
1209
|
+
"type": {
|
|
1210
|
+
"text": "string | undefined"
|
|
1211
|
+
},
|
|
1212
|
+
"description": "With href: the link's relationship to this page, as `<a rel>`.",
|
|
1213
|
+
"fieldName": "rel"
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
"name": "download",
|
|
1217
|
+
"type": {
|
|
1218
|
+
"text": "string | undefined"
|
|
1219
|
+
},
|
|
1220
|
+
"description": "With href: download the resource instead of navigating to it, as\n`<a download>` — the value, when there is one, names the saved file.",
|
|
1221
|
+
"fieldName": "download"
|
|
1222
|
+
},
|
|
1113
1223
|
{
|
|
1114
1224
|
"name": "top",
|
|
1115
1225
|
"type": {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
1
2
|
import { VfShadowRoleControl } from '../form-control.js';
|
|
2
3
|
declare const VfButton_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof VfShadowRoleControl;
|
|
3
4
|
/**
|
|
@@ -30,6 +31,13 @@ declare const VfButton_base: (new (...args: any[]) => import("../position.js").V
|
|
|
30
31
|
* `event.submitter.closest('vf-button')`, and its identity from
|
|
31
32
|
* `submitter.name`/`.value` rather than by comparing element references.
|
|
32
33
|
*
|
|
34
|
+
* With an `href` it is a link instead: the same art over an `<a href>` in
|
|
35
|
+
* place of the `<button>`, so it is followed the way any link is — a click,
|
|
36
|
+
* Enter, a modified or middle click into a new tab, the context menu's link
|
|
37
|
+
* items — and `target`, `rel` and `download` mean what they mean on `<a>`.
|
|
38
|
+
* A link is not a form button: `type`, `name`, `value` and the `form*`
|
|
39
|
+
* overrides do nothing while `href` is set.
|
|
40
|
+
*
|
|
33
41
|
* A host-level `aria-label` / `aria-labelledby` names the inner button, and
|
|
34
42
|
* `description` (or a host-level `aria-describedby`) describes it — the same
|
|
35
43
|
* bridge the fields use, since the role lives on a shadow-internal node the
|
|
@@ -37,7 +45,8 @@ declare const VfButton_base: (new (...args: any[]) => import("../position.js").V
|
|
|
37
45
|
* name it: a `<button>` is not a labelable element.
|
|
38
46
|
*
|
|
39
47
|
* @slot - The button label.
|
|
40
|
-
* @csspart button - The inner native `<button>` element
|
|
48
|
+
* @csspart button - The inner native `<button>` element — an `<a>` with
|
|
49
|
+
* `href`.
|
|
41
50
|
* @cssprop [--vf-button-height=20px] - `vf-button` face (the default ring's
|
|
42
51
|
* inner box is 80×20)
|
|
43
52
|
*/
|
|
@@ -91,11 +100,41 @@ export declare class VfButton extends VfButton_base {
|
|
|
91
100
|
formNoValidate: boolean;
|
|
92
101
|
/** See {@link formAction}. Overrides the form's `target`. */
|
|
93
102
|
formTarget: string;
|
|
103
|
+
/**
|
|
104
|
+
* A URL, and the button becomes a link to it: an `<a href>` renders in
|
|
105
|
+
* place of the inner `<button>` under the same art, and the platform
|
|
106
|
+
* follows it — a click, Enter, a modified or middle click, the context
|
|
107
|
+
* menu's link items. An empty value is no link.
|
|
108
|
+
*
|
|
109
|
+
* A link is not a form button. While `href` is set, {@link type},
|
|
110
|
+
* {@link name}, {@link value} and the `form*` overrides do nothing, and the
|
|
111
|
+
* button is never a form's default button for a field's Enter. Inside a
|
|
112
|
+
* `vf-dialog`, `variant="default"` still makes it the dialog's default
|
|
113
|
+
* button, so Return follows the link.
|
|
114
|
+
*
|
|
115
|
+
* Disabled — by attribute or an ancestor `<fieldset disabled>` — the `<a>`
|
|
116
|
+
* keeps rendering but loses its `href`, taking `role="link"` and
|
|
117
|
+
* `aria-disabled="true"` instead: out of the tab order, and inert to the
|
|
118
|
+
* pointer and to {@link click}, as a disabled `<button>` is.
|
|
119
|
+
*/
|
|
120
|
+
href?: string;
|
|
121
|
+
/** With {@link href}: where the link opens, as `<a target>`. */
|
|
122
|
+
target?: string;
|
|
123
|
+
/** With {@link href}: the link's relationship to this page, as `<a rel>`. */
|
|
124
|
+
rel?: string;
|
|
125
|
+
/**
|
|
126
|
+
* With {@link href}: download the resource instead of navigating to it, as
|
|
127
|
+
* `<a download>` — the value, when there is one, names the saved file.
|
|
128
|
+
*/
|
|
129
|
+
download?: string;
|
|
94
130
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
95
131
|
private readonly scale;
|
|
96
132
|
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
97
133
|
private readonly gridSnap;
|
|
98
|
-
|
|
134
|
+
/** The inner `<button>` — or `<a>`, under {@link href}. */
|
|
135
|
+
private control;
|
|
136
|
+
/** Whether {@link href} makes this a link. */
|
|
137
|
+
private get isLink();
|
|
99
138
|
/**
|
|
100
139
|
* {@link type}, resolved the way HTML resolves an enumerated attribute:
|
|
101
140
|
* ASCII case-insensitively, with anything unrecognized falling to the
|
|
@@ -115,7 +154,10 @@ export declare class VfButton extends VfButton_base {
|
|
|
115
154
|
* actual activation surface, is handed the call instead. Its click then
|
|
116
155
|
* bubbles back out composed, exactly as a pointer's does. This is what the
|
|
117
156
|
* fields' implicit submission (Enter) activates as the form's default
|
|
118
|
-
* button; a disabled button swallows it natively, as it should.
|
|
157
|
+
* button; a disabled button swallows it natively, as it should. Under
|
|
158
|
+
* {@link href} the `<a>` is handed the call and follows itself — which is
|
|
159
|
+
* how a dialog's Return reaches a default button that is a link. A
|
|
160
|
+
* disabled link has no native state to swallow it, so it is withheld here.
|
|
119
161
|
*
|
|
120
162
|
* Before the first render there is no inner button to forward to. A native
|
|
121
163
|
* `click()` always fires, so rather than doing nothing this falls back to
|
|
@@ -123,7 +165,14 @@ export declare class VfButton extends VfButton_base {
|
|
|
123
165
|
* event a caller asked for.
|
|
124
166
|
*/
|
|
125
167
|
click(): void;
|
|
126
|
-
render():
|
|
168
|
+
render(): TemplateResult<1>;
|
|
169
|
+
private renderButton;
|
|
170
|
+
/**
|
|
171
|
+
* The link. No click listener: following it is the platform's activation
|
|
172
|
+
* behavior. A disabled one is an `<a>` without an `href`, which has no
|
|
173
|
+
* link role unless it is given one.
|
|
174
|
+
*/
|
|
175
|
+
private renderLink;
|
|
127
176
|
/**
|
|
128
177
|
* The click that may become a submission or a reset.
|
|
129
178
|
*
|
|
@@ -90,8 +90,9 @@ var S = "vf-submitter", C = class extends i(h) {
|
|
|
90
90
|
the stepped pseudo-element silhouettes below. Keeping the clip-paths
|
|
91
91
|
off the button itself leaves the hit area a plain rectangle, and
|
|
92
92
|
anything painted outside the silhouette (a ring a consumer restores
|
|
93
|
-
on ::part(button)) unclipped.
|
|
94
|
-
|
|
93
|
+
on ::part(button)) unclipped. .control is that button, or the <a>
|
|
94
|
+
that stands in for it under href: the art is the same either way. */
|
|
95
|
+
.control {
|
|
95
96
|
position: relative;
|
|
96
97
|
/* Own stacking context so the negative-z silhouettes stay inside the
|
|
97
98
|
button: above everything behind it, below the label. */
|
|
@@ -118,31 +119,40 @@ var S = "vf-submitter", C = class extends i(h) {
|
|
|
118
119
|
line-height: inherit;
|
|
119
120
|
white-space: nowrap;
|
|
120
121
|
cursor: inherit;
|
|
122
|
+
/* What the UA sheet gives a <button> and an <a> does not: no
|
|
123
|
+
underline, and none of the page's text styling inherited through
|
|
124
|
+
the host — so the link reads exactly as the button does. */
|
|
125
|
+
text-decoration: none;
|
|
126
|
+
text-transform: none;
|
|
127
|
+
text-indent: 0;
|
|
128
|
+
text-shadow: none;
|
|
129
|
+
letter-spacing: normal;
|
|
130
|
+
word-spacing: normal;
|
|
121
131
|
}
|
|
122
132
|
/* Frame: the outer silhouette in solid black. The face below covers all
|
|
123
133
|
but the outline, leaving the reference's exact border pixels — the
|
|
124
134
|
QuickDraw difference-of-silhouettes, not a stroked border. */
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
.control::before,
|
|
136
|
+
.control::after {
|
|
127
137
|
content: '';
|
|
128
138
|
position: absolute;
|
|
129
139
|
inset: 0;
|
|
130
140
|
z-index: -1;
|
|
131
141
|
}
|
|
132
|
-
|
|
142
|
+
.control::before {
|
|
133
143
|
background: var(--vf-black, #000);
|
|
134
144
|
clip-path: ${y(p(u))};
|
|
135
145
|
}
|
|
136
146
|
/* Face: white fill inset one pixel, with its own traced corner steps. */
|
|
137
|
-
|
|
147
|
+
.control::after {
|
|
138
148
|
background: var(--vf-white, #fff);
|
|
139
149
|
clip-path: ${y(p(l))};
|
|
140
150
|
}
|
|
141
151
|
/* Pressed: instant white-on-black inversion. */
|
|
142
|
-
|
|
152
|
+
.control:active:not(:disabled) {
|
|
143
153
|
color: var(--vf-white, #fff);
|
|
144
154
|
}
|
|
145
|
-
|
|
155
|
+
.control:active:not(:disabled)::after {
|
|
146
156
|
background: var(--vf-black, #000);
|
|
147
157
|
}
|
|
148
158
|
/* The label rides in its own box so the focus underline can span the
|
|
@@ -154,7 +164,7 @@ var S = "vf-submitter", C = class extends i(h) {
|
|
|
154
164
|
}
|
|
155
165
|
/* Keyboard focus is the dashed rule under the label, not a ring around
|
|
156
166
|
the control — so the UA's own outline goes. */
|
|
157
|
-
|
|
167
|
+
.control:focus-visible {
|
|
158
168
|
outline: none;
|
|
159
169
|
}
|
|
160
170
|
/* …and off the host too. Blink doesn't currently propagate
|
|
@@ -166,39 +176,77 @@ var S = "vf-submitter", C = class extends i(h) {
|
|
|
166
176
|
:host(:focus-visible) {
|
|
167
177
|
outline: none;
|
|
168
178
|
}
|
|
169
|
-
|
|
179
|
+
.control:focus-visible .label::after {
|
|
170
180
|
${s}
|
|
171
181
|
}
|
|
172
182
|
/* Disabled: only the label dims to gray; the solid black border stays. */
|
|
173
|
-
|
|
183
|
+
.control:disabled {
|
|
174
184
|
color: var(--vf-disabled, #c0c0c0);
|
|
175
185
|
}
|
|
186
|
+
/* A disabled link has no disabled state of its own, so it is spelled
|
|
187
|
+
out: the same dimmed label, and no press. Letting the pointer through
|
|
188
|
+
to the host keeps the face from inverting (:active never reaches the
|
|
189
|
+
link) and makes the press's target the host itself — a disabled form
|
|
190
|
+
control, which the platform dispatches no click to, as it dispatches
|
|
191
|
+
none to a disabled <button>. */
|
|
192
|
+
a.control[aria-disabled='true'] {
|
|
193
|
+
color: var(--vf-disabled, #c0c0c0);
|
|
194
|
+
pointer-events: none;
|
|
195
|
+
}
|
|
176
196
|
`
|
|
177
197
|
];
|
|
178
198
|
}
|
|
199
|
+
get isLink() {
|
|
200
|
+
return !!this.href;
|
|
201
|
+
}
|
|
179
202
|
#e;
|
|
180
203
|
get resolvedType() {
|
|
181
204
|
let e = String(this.type ?? "").toLowerCase();
|
|
182
205
|
return e === "submit" ? "submit" : e === "reset" ? "reset" : "button";
|
|
183
206
|
}
|
|
184
207
|
click() {
|
|
185
|
-
let e = this.
|
|
186
|
-
e ? e.click() : super.click();
|
|
208
|
+
let e = this.control;
|
|
209
|
+
e ? this.isLink && this.isDisabled || e.click() : super.click();
|
|
187
210
|
}
|
|
188
211
|
render() {
|
|
212
|
+
let e = _`<span class="label"><slot></slot></span>`;
|
|
213
|
+
return _`
|
|
214
|
+
${this.isLink ? this.renderLink(e) : this.renderButton(e)}
|
|
215
|
+
${this.renderDescription()}
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
renderButton(e) {
|
|
189
219
|
return _`
|
|
190
220
|
<button
|
|
191
221
|
part="button"
|
|
192
|
-
class="vf-snap"
|
|
222
|
+
class="control vf-snap"
|
|
193
223
|
type="button"
|
|
194
224
|
aria-label=${this.hostAriaLabel || v}
|
|
195
225
|
aria-describedby=${this.describedBy}
|
|
196
226
|
?disabled=${this.isDisabled}
|
|
197
227
|
@click=${this.handleClick}
|
|
198
228
|
>
|
|
199
|
-
|
|
229
|
+
${e}
|
|
200
230
|
</button>
|
|
201
|
-
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
renderLink(e) {
|
|
234
|
+
let t = this.isDisabled;
|
|
235
|
+
return _`
|
|
236
|
+
<a
|
|
237
|
+
part="button"
|
|
238
|
+
class="control vf-snap"
|
|
239
|
+
href=${t ? v : this.href}
|
|
240
|
+
target=${this.target || v}
|
|
241
|
+
rel=${this.rel || v}
|
|
242
|
+
download=${this.download ?? v}
|
|
243
|
+
role=${t ? "link" : v}
|
|
244
|
+
aria-disabled=${t ? "true" : v}
|
|
245
|
+
aria-label=${this.hostAriaLabel || v}
|
|
246
|
+
aria-describedby=${this.describedBy}
|
|
247
|
+
>
|
|
248
|
+
${e}
|
|
249
|
+
</a>
|
|
202
250
|
`;
|
|
203
251
|
}
|
|
204
252
|
activate() {
|
|
@@ -229,6 +277,6 @@ r([b({ reflect: !0 })], C.prototype, "variant", void 0), r([b({ reflect: !0 })],
|
|
|
229
277
|
})], C.prototype, "formNoValidate", void 0), r([b({
|
|
230
278
|
attribute: "formtarget",
|
|
231
279
|
reflect: !0
|
|
232
|
-
})], C.prototype, "formTarget", void 0), r([x("
|
|
280
|
+
})], C.prototype, "formTarget", void 0), r([b({ reflect: !0 })], C.prototype, "href", void 0), r([b({ reflect: !0 })], C.prototype, "target", void 0), r([b({ reflect: !0 })], C.prototype, "rel", void 0), r([b({ reflect: !0 })], C.prototype, "download", void 0), r([x(".control")], C.prototype, "control", void 0), C = r([t("vf-button")], C);
|
|
233
281
|
//#endregion
|
|
234
282
|
export { C as VfButton };
|
package/dist/text-control.js
CHANGED
|
@@ -57,7 +57,7 @@ var l = class extends a {
|
|
|
57
57
|
requestImplicitSubmit() {
|
|
58
58
|
let e = this.internals.form;
|
|
59
59
|
if (!e) return !1;
|
|
60
|
-
let t = [...e.elements].find((e) => e instanceof HTMLButtonElement && e.type === "submit" || e instanceof HTMLInputElement && (e.type === "submit" || e.type === "image") || e.localName === "vf-button" && e.type?.toLowerCase() === "submit");
|
|
60
|
+
let t = [...e.elements].find((e) => e instanceof HTMLButtonElement && e.type === "submit" || e instanceof HTMLInputElement && (e.type === "submit" || e.type === "image") || e.localName === "vf-button" && !e.href && e.type?.toLowerCase() === "submit");
|
|
61
61
|
return t ? (t.matches(":disabled") || t.click(), !0) : (e.requestSubmit(), !0);
|
|
62
62
|
}
|
|
63
63
|
emitValue(t, n = { value: this.value }) {
|
package/docs/SPEC.md
CHANGED
|
@@ -250,8 +250,8 @@ System 7's fixed modal alert (double frame: 2px outer, 2px gap, 1px inner rule,
|
|
|
250
250
|
### Group B — buttons & toggles
|
|
251
251
|
|
|
252
252
|
#### `vf-button` (`VfButton`, vf-button.ts)
|
|
253
|
-
- **Attributes/props:** `variant?: 'default'` (the double-ring default button, e.g. "Install"), `disabled`, `type: 'button' | 'submit' | 'reset'` (reflected; default `'button'`), plus the submission overrides `formaction`, `formenctype`, `formmethod`, `formnovalidate` and `formtarget`, each honored on `type="submit"` only, as HTML honors them — they are handed to the native proxy below, so the behavior is the platform's rather than an emulation. (`formAction`'s *getter* returns the string as set, where the native IDL returns it resolved against the base URL; the submission resolves normally.) `type` is read the way HTML reads an enumerated attribute — ASCII case-insensitively, unrecognized values falling to the default — so `type="SUBMIT"` is a submit button. Two deliberate departures: HTML's missing-value default is `submit` and this one is `button`, because an element that silently submits the form it sits in is surprising; and the invalid-value default follows the missing one rather than HTML's `submit`, so a misspelling does nothing instead of submitting.
|
|
254
|
-
- **Visual:** inner `<button
|
|
253
|
+
- **Attributes/props:** `variant?: 'default'` (the double-ring default button, e.g. "Install"), `disabled`, `type: 'button' | 'submit' | 'reset'` (reflected; default `'button'`), plus the submission overrides `formaction`, `formenctype`, `formmethod`, `formnovalidate` and `formtarget`, each honored on `type="submit"` only, as HTML honors them — they are handed to the native proxy below, so the behavior is the platform's rather than an emulation. (`formAction`'s *getter* returns the string as set, where the native IDL returns it resolved against the base URL; the submission resolves normally.) `type` is read the way HTML reads an enumerated attribute — ASCII case-insensitively, unrecognized values falling to the default — so `type="SUBMIT"` is a submit button. Two deliberate departures: HTML's missing-value default is `submit` and this one is `button`, because an element that silently submits the form it sits in is surprising; and the invalid-value default follows the missing one rather than HTML's `submit`, so a misspelling does nothing instead of submitting. `href` (optional, reflected; unset by default so no `href=""` lands on every host) with `target`, `rel` and `download` makes it a link — see **Link** below.
|
|
254
|
+
- **Visual:** inner `<button>` (an `<a>` under `href`, carrying the same `.control` rules): height `var(--vf-button-height, 20px)`, `min-width: 64px`, `padding: 0 14px`, bold black text, font per tokens. The button is 20px, not the fields' 22px: both 1x sheets measure the face at 80×20, and the default ring's *inner* box is exactly that, so the ring traces assume a 20px face. The label rides in its own `.label` span (the flex item, shrink-wrapped to the text) so the focus rule below can span the text rather than the padded face. The rounded rect is NOT `border-radius` (which antialiases): the button paints no box of its own; two pseudo-element layers carry stepped `clip-path` silhouettes traced from the reference sheet (`src/pixel-frame.ts`, traced from the 1x button reference sheet; `npm run verify:buttons` checks the compiled polygons render those profiles faithfully): `::before` fills `var(--vf-black)` clipped to the outer silhouette (corner insets `[3,1,1]`, then straight), `::after` fills `var(--vf-white)` clipped to the face (row 1 corner insets `[3,2]`, then 1px inside) — the 1px frame, corner steps included, is the QuickDraw-style difference of the two. Clip-paths stay off the `<button>` so the hit area stays a plain rectangle and anything painted outside the silhouette isn't swallowed. All coordinates are `calc(var(--vf-scale,1) * Npx)` system pixels, so edges land on whole device pixels and never antialias.
|
|
255
255
|
- `:active` (pressed, not disabled): invert — the `::after` face flips to black, white text.
|
|
256
256
|
- `:focus-visible`: **no ring** — the UA outline is off on both the inner button and the (`delegatesFocus`) host, replaced by `vfFocusUnderline` (§4) on the label: a 1px dashed rule 1px below the baseline, i.e. row 15 of the 20px face, one blank row under the glyph ink. It inverts with the label when pressed; the controls that cannot carry the mark on their own face keep the dotted ring. `npm run verify:focus` and the focused button in `npm run shot:verify` assert the rendered pixels.
|
|
257
257
|
- `disabled`: only the label dims to `var(--vf-disabled, #c0c0c0)`; the 1px black border stays black. (For `variant="default"`, the fat outer ring dims to `var(--vf-disabled)` while the inner black border stays — the ring and the title dim together, the button's own frame does not. System 7 dimmed both with a 50% stipple; flat gray standing in for it is the kit's liberty, per §1.) The ring rule is keyed on **both** disabled routes — `[disabled]` and `:state(form-disabled)` — as separate rules: an ancestor `<fieldset disabled>` arrives through `formDisabledCallback` and sets no attribute, and keyed on the attribute alone it left a solid black ring around a greyed label.
|
|
@@ -260,10 +260,11 @@ System 7's fixed modal alert (double frame: 2px outer, 2px gap, 1px inner rule,
|
|
|
260
260
|
- **Behavior:** form-associated. `type="submit"` submits by inserting a transient native proxy button carrying this button's `name`/`value` and any `form*` override, then clicking it — a form-associated custom element can't be a native submitter itself (`requestSubmit(vfButton)` throws a `TypeError`, "not a submit button"), and a bare `requestSubmit()` would submit with `event.submitter === null` and no name/value; `reset` → the same proxy path. Enter/Space work natively via the inner button, and the host overrides `click()` to forward to it (a click dispatched at the host propagates up, never down into the shadow tree; before the first render there is no inner button, so it falls back to dispatching at the host, since a native `click()` always fires) — which is also how the fields' Enter reaches this button as the form's default button (see `requestImplicitSubmit`, text-control.ts; its `vf-button` leg lowercases `type` for the enumerated rule above).
|
|
261
261
|
- **When the action runs.** At the *end* of the click's propagation, not where the listener sits. HTML runs a button's activation behavior once the click has finished propagating, which is what lets `preventDefault()` on the button cancel the submission; this component's listener is on the inner `<button>`, first on that path rather than last, so acting there beat every listener a consumer could write and only a capture-phase cancel ever landed. It now defers by adding a listener to the window *during* dispatch (each node's listener list is read as that node is reached), so both phases cancel. `stopPropagation()` cancels nothing, per HTML — the event then never reaches the window and a task picks the action up instead; a task and never a microtask, which would interleave between the listeners of a trusted dispatch.
|
|
262
262
|
- **Submitter identity.** `event.submitter` can never *be* the `vf-button` — the platform forbids it. It is the proxy, which is parented to the host and carries a `slot` name no shadow root offers: unslotted, so it is never rendered, measured or in the a11y tree, and off the flattened tree so its own click can't travel back up through the shadow `<button>` the click handler is bound to. Being a light-DOM descendant is what makes `event.submitter.closest('vf-button')` resolve to the component — the closest to identity the platform allows. Its own click is stopped at the proxy, so one press is one click at the form (`stopPropagation` leaves the activation behavior alone).
|
|
263
|
+
- **Link:** a non-empty `href` renders an `<a part="button" href target rel download>` in place of the `<button>`, with no click listener of its own: following it — a click, Enter, a modified or middle click into a new tab, the context menu's link items — is the platform's. The art is identical at rest, focused and pressed; the `.control` rule adds the resets the UA sheet gives a `<button>` and not an `<a>` (`text-decoration`, `text-transform`, `text-indent`, `text-shadow`, `letter-spacing`, `word-spacing`), so the host's inherited text styling can't reach the label. A link is not a form button: `type`, `name`, `value` and the `form*` overrides do nothing, and `requestImplicitSubmit` skips a `vf-button` with an `href` whatever its `type`. The host stays form-associated (it cannot opt out per instance), so `disabled` and an ancestor `<fieldset disabled>` still disable it. Disabled, the `<a>` drops its `href` and takes `role="link"` + `aria-disabled="true"` — out of the tab order, label dimmed, ring dimmed — with `pointer-events: none`, which keeps the face from inverting and makes a press land on the disabled host, which gets no click; `click()` is withheld outright, since a disabled `<a>` has no native state to swallow it. `click()` on an enabled link forwards to the `<a>`, so a `vf-dialog`'s Return follows a `variant="default"` link, and the dialog's keydown leaves Enter on a focused link to the link (its composed path carries the `<a href>`).
|
|
263
264
|
- **Name:** the slotted label, with a host-level `aria-label`/`aria-labelledby` bridged to the inner button when present (see §4's name bridge); a `<label for>` deliberately doesn't name it. `description` and a host-level `aria-describedby` reach it through the same shadow-internal span the fields use.
|
|
264
|
-
- **Slots:** default (label). **Parts:** `button
|
|
265
|
+
- **Slots:** default (label). **Parts:** `button` (the `<button>`, or the `<a>` under `href`).
|
|
265
266
|
- **Events:** none custom (native `click` suffices).
|
|
266
|
-
- **Verified by:** `npm run verify:button` (the form contract, the name bridge, the enumerated `type`, both disabled routes and the
|
|
267
|
+
- **Verified by:** `npm run verify:button` (the form contract, the name bridge, the enumerated `type`, both disabled routes, the pinned metrics, and the link: role, following by click/Enter/`click()`/modified click, the disabled link, form inertness, a dialog's default link and pixel parity with the button); `verify:buttons` (the clip-path traces), `verify:focus` (the dashed rule).
|
|
267
268
|
|
|
268
269
|
#### `vf-button-group` (`VfButtonGroup`, vf-button-group.ts)
|
|
269
270
|
- **Attributes/props:** `vertical` (stack in a column instead of a row), `natural` (let each button keep its own content width; off by default, so grouped buttons are uniform width — the classic System 7 dialog behavior).
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "vf-button",
|
|
44
|
-
"description": "The classic System 7 push button (\"OK\", \"Cancel\", \"Install\", …).\n\nA rounded-rectangle control with a 1px black border and white face that\ninverts to white-on-black while pressed. `variant=\"default\"` draws the\nclassic bold double ring around the button, marking it as the default\naction of a dialog.\n\nThe rounded corners are not `border-radius` arcs: frame, face, and ring are\nstepped `clip-path` silhouettes traced pixel-for-pixel from the kit's button\nreference sheet (see `src/pixel-frame.ts`), so every corner renders as the\nexact 1-bit staircase with no antialiasing.\n\nKeyboard focus — an affordance System 7 didn't have, drawn in its idiom\nanyway — is a 1px dashed rule under the label text (`vfFocusUnderline`)\nrather than a ring around the control.\n\nForm-associated: place it inside a `<form>` and `type=\"submit\"` submits the\nform (contributing its `name`/`value` and any `form*` override to the\nsubmission), `type=\"reset\"` resets it. Enter and Space activate it via the\ninner native button, and the submission runs at the end of the click's\npropagation, so `preventDefault()` on the button cancels it the way it\ncancels a native one.\n\nOne thing the platform will not allow: `event.submitter` cannot BE a\nform-associated custom element. It is the transient native proxy this\nbutton submits through — so read the submitting button as\n`event.submitter.closest('vf-button')`, and its identity from\n`submitter.name`/`.value` rather than by comparing element references.\n\nA host-level `aria-label` / `aria-labelledby` names the inner button, and\n`description` (or a host-level `aria-describedby`) describes it — the same\nbridge the fields use, since the role lives on a shadow-internal node the\nplatform can't deliver those to. A `<label for>` deliberately does not\nname it: a `<button>` is not a labelable element.\n---\n\n\n### **Slots:**\n - _default_ - The button label.\n\n### **CSS Properties:**\n - **--vf-button-height** - `vf-button` face (the default ring's inner box is 80×20) _(default: 20px)_\n\n### **CSS Parts:**\n - **button** - The inner native `<button>` element
|
|
44
|
+
"description": "The classic System 7 push button (\"OK\", \"Cancel\", \"Install\", …).\n\nA rounded-rectangle control with a 1px black border and white face that\ninverts to white-on-black while pressed. `variant=\"default\"` draws the\nclassic bold double ring around the button, marking it as the default\naction of a dialog.\n\nThe rounded corners are not `border-radius` arcs: frame, face, and ring are\nstepped `clip-path` silhouettes traced pixel-for-pixel from the kit's button\nreference sheet (see `src/pixel-frame.ts`), so every corner renders as the\nexact 1-bit staircase with no antialiasing.\n\nKeyboard focus — an affordance System 7 didn't have, drawn in its idiom\nanyway — is a 1px dashed rule under the label text (`vfFocusUnderline`)\nrather than a ring around the control.\n\nForm-associated: place it inside a `<form>` and `type=\"submit\"` submits the\nform (contributing its `name`/`value` and any `form*` override to the\nsubmission), `type=\"reset\"` resets it. Enter and Space activate it via the\ninner native button, and the submission runs at the end of the click's\npropagation, so `preventDefault()` on the button cancels it the way it\ncancels a native one.\n\nOne thing the platform will not allow: `event.submitter` cannot BE a\nform-associated custom element. It is the transient native proxy this\nbutton submits through — so read the submitting button as\n`event.submitter.closest('vf-button')`, and its identity from\n`submitter.name`/`.value` rather than by comparing element references.\n\nWith an `href` it is a link instead: the same art over an `<a href>` in\nplace of the `<button>`, so it is followed the way any link is — a click,\nEnter, a modified or middle click into a new tab, the context menu's link\nitems — and `target`, `rel` and `download` mean what they mean on `<a>`.\nA link is not a form button: `type`, `name`, `value` and the `form*`\noverrides do nothing while `href` is set.\n\nA host-level `aria-label` / `aria-labelledby` names the inner button, and\n`description` (or a host-level `aria-describedby`) describes it — the same\nbridge the fields use, since the role lives on a shadow-internal node the\nplatform can't deliver those to. A `<label for>` deliberately does not\nname it: a `<button>` is not a labelable element.\n---\n\n\n### **Slots:**\n - _default_ - The button label.\n\n### **CSS Properties:**\n - **--vf-button-height** - `vf-button` face (the default ring's inner box is 80×20) _(default: 20px)_\n\n### **CSS Parts:**\n - **button** - The inner native `<button>` element — an `<a>` with `href`.",
|
|
45
45
|
"attributes": [
|
|
46
46
|
{
|
|
47
47
|
"name": "variant",
|
|
@@ -92,6 +92,26 @@
|
|
|
92
92
|
"description": "See formAction. Overrides the form's `target`.",
|
|
93
93
|
"values": []
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
"name": "href",
|
|
97
|
+
"description": "A URL, and the button becomes a link to it: an `<a href>` renders in\nplace of the inner `<button>` under the same art, and the platform\nfollows it — a click, Enter, a modified or middle click, the context\nmenu's link items. An empty value is no link.\n\nA link is not a form button. While `href` is set, type,\nname, value and the `form*` overrides do nothing, and the\nbutton is never a form's default button for a field's Enter. Inside a\n`vf-dialog`, `variant=\"default\"` still makes it the dialog's default\nbutton, so Return follows the link.\n\nDisabled — by attribute or an ancestor `<fieldset disabled>` — the `<a>`\nkeeps rendering but loses its `href`, taking `role=\"link\"` and\n`aria-disabled=\"true\"` instead: out of the tab order, and inert to the\npointer and to click, as a disabled `<button>` is.",
|
|
98
|
+
"values": []
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "target",
|
|
102
|
+
"description": "With href: where the link opens, as `<a target>`.",
|
|
103
|
+
"values": []
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "rel",
|
|
107
|
+
"description": "With href: the link's relationship to this page, as `<a rel>`.",
|
|
108
|
+
"values": []
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "download",
|
|
112
|
+
"description": "With href: download the resource instead of navigating to it, as\n`<a download>` — the value, when there is one, names the saved file.",
|
|
113
|
+
"values": []
|
|
114
|
+
},
|
|
95
115
|
{
|
|
96
116
|
"name": "top",
|
|
97
117
|
"description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
|
package/editor/web-types.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
|
|
3
3
|
"name": "vintage-frames",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.5",
|
|
5
5
|
"description-markup": "markdown",
|
|
6
6
|
"contributions": {
|
|
7
7
|
"html": {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"name": "vf-button",
|
|
88
|
-
"description": "The classic System 7 push button (\"OK\", \"Cancel\", \"Install\", …).\n\nA rounded-rectangle control with a 1px black border and white face that\ninverts to white-on-black while pressed. `variant=\"default\"` draws the\nclassic bold double ring around the button, marking it as the default\naction of a dialog.\n\nThe rounded corners are not `border-radius` arcs: frame, face, and ring are\nstepped `clip-path` silhouettes traced pixel-for-pixel from the kit's button\nreference sheet (see `src/pixel-frame.ts`), so every corner renders as the\nexact 1-bit staircase with no antialiasing.\n\nKeyboard focus — an affordance System 7 didn't have, drawn in its idiom\nanyway — is a 1px dashed rule under the label text (`vfFocusUnderline`)\nrather than a ring around the control.\n\nForm-associated: place it inside a `<form>` and `type=\"submit\"` submits the\nform (contributing its `name`/`value` and any `form*` override to the\nsubmission), `type=\"reset\"` resets it. Enter and Space activate it via the\ninner native button, and the submission runs at the end of the click's\npropagation, so `preventDefault()` on the button cancels it the way it\ncancels a native one.\n\nOne thing the platform will not allow: `event.submitter` cannot BE a\nform-associated custom element. It is the transient native proxy this\nbutton submits through — so read the submitting button as\n`event.submitter.closest('vf-button')`, and its identity from\n`submitter.name`/`.value` rather than by comparing element references.\n\nA host-level `aria-label` / `aria-labelledby` names the inner button, and\n`description` (or a host-level `aria-describedby`) describes it — the same\nbridge the fields use, since the role lives on a shadow-internal node the\nplatform can't deliver those to. A `<label for>` deliberately does not\nname it: a `<button>` is not a labelable element.\n---\n\n\n### **Slots:**\n - _default_ - The button label.\n\n### **CSS Properties:**\n - **--vf-button-height** - `vf-button` face (the default ring's inner box is 80×20) _(default: 20px)_\n\n### **CSS Parts:**\n - **button** - The inner native `<button>` element
|
|
88
|
+
"description": "The classic System 7 push button (\"OK\", \"Cancel\", \"Install\", …).\n\nA rounded-rectangle control with a 1px black border and white face that\ninverts to white-on-black while pressed. `variant=\"default\"` draws the\nclassic bold double ring around the button, marking it as the default\naction of a dialog.\n\nThe rounded corners are not `border-radius` arcs: frame, face, and ring are\nstepped `clip-path` silhouettes traced pixel-for-pixel from the kit's button\nreference sheet (see `src/pixel-frame.ts`), so every corner renders as the\nexact 1-bit staircase with no antialiasing.\n\nKeyboard focus — an affordance System 7 didn't have, drawn in its idiom\nanyway — is a 1px dashed rule under the label text (`vfFocusUnderline`)\nrather than a ring around the control.\n\nForm-associated: place it inside a `<form>` and `type=\"submit\"` submits the\nform (contributing its `name`/`value` and any `form*` override to the\nsubmission), `type=\"reset\"` resets it. Enter and Space activate it via the\ninner native button, and the submission runs at the end of the click's\npropagation, so `preventDefault()` on the button cancels it the way it\ncancels a native one.\n\nOne thing the platform will not allow: `event.submitter` cannot BE a\nform-associated custom element. It is the transient native proxy this\nbutton submits through — so read the submitting button as\n`event.submitter.closest('vf-button')`, and its identity from\n`submitter.name`/`.value` rather than by comparing element references.\n\nWith an `href` it is a link instead: the same art over an `<a href>` in\nplace of the `<button>`, so it is followed the way any link is — a click,\nEnter, a modified or middle click into a new tab, the context menu's link\nitems — and `target`, `rel` and `download` mean what they mean on `<a>`.\nA link is not a form button: `type`, `name`, `value` and the `form*`\noverrides do nothing while `href` is set.\n\nA host-level `aria-label` / `aria-labelledby` names the inner button, and\n`description` (or a host-level `aria-describedby`) describes it — the same\nbridge the fields use, since the role lives on a shadow-internal node the\nplatform can't deliver those to. A `<label for>` deliberately does not\nname it: a `<button>` is not a labelable element.\n---\n\n\n### **Slots:**\n - _default_ - The button label.\n\n### **CSS Properties:**\n - **--vf-button-height** - `vf-button` face (the default ring's inner box is 80×20) _(default: 20px)_\n\n### **CSS Parts:**\n - **button** - The inner native `<button>` element — an `<a>` with `href`.",
|
|
89
89
|
"doc-url": "",
|
|
90
90
|
"attributes": [
|
|
91
91
|
{
|
|
@@ -136,6 +136,26 @@
|
|
|
136
136
|
"description": "See formAction. Overrides the form's `target`.",
|
|
137
137
|
"value": { "type": "string", "default": "''" }
|
|
138
138
|
},
|
|
139
|
+
{
|
|
140
|
+
"name": "href",
|
|
141
|
+
"description": "A URL, and the button becomes a link to it: an `<a href>` renders in\nplace of the inner `<button>` under the same art, and the platform\nfollows it — a click, Enter, a modified or middle click, the context\nmenu's link items. An empty value is no link.\n\nA link is not a form button. While `href` is set, type,\nname, value and the `form*` overrides do nothing, and the\nbutton is never a form's default button for a field's Enter. Inside a\n`vf-dialog`, `variant=\"default\"` still makes it the dialog's default\nbutton, so Return follows the link.\n\nDisabled — by attribute or an ancestor `<fieldset disabled>` — the `<a>`\nkeeps rendering but loses its `href`, taking `role=\"link\"` and\n`aria-disabled=\"true\"` instead: out of the tab order, and inert to the\npointer and to click, as a disabled `<button>` is.",
|
|
142
|
+
"value": { "type": "string | undefined" }
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "target",
|
|
146
|
+
"description": "With href: where the link opens, as `<a target>`.",
|
|
147
|
+
"value": { "type": "string | undefined" }
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "rel",
|
|
151
|
+
"description": "With href: the link's relationship to this page, as `<a rel>`.",
|
|
152
|
+
"value": { "type": "string | undefined" }
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "download",
|
|
156
|
+
"description": "With href: download the resource instead of navigating to it, as\n`<a download>` — the value, when there is one, names the saved file.",
|
|
157
|
+
"value": { "type": "string | undefined" }
|
|
158
|
+
},
|
|
139
159
|
{
|
|
140
160
|
"name": "top",
|
|
141
161
|
"description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
|
|
@@ -221,6 +241,26 @@
|
|
|
221
241
|
"description": "See formAction. Overrides the form's `target`.",
|
|
222
242
|
"type": "string"
|
|
223
243
|
},
|
|
244
|
+
{
|
|
245
|
+
"name": "href",
|
|
246
|
+
"description": "A URL, and the button becomes a link to it: an `<a href>` renders in\nplace of the inner `<button>` under the same art, and the platform\nfollows it — a click, Enter, a modified or middle click, the context\nmenu's link items. An empty value is no link.\n\nA link is not a form button. While `href` is set, type,\nname, value and the `form*` overrides do nothing, and the\nbutton is never a form's default button for a field's Enter. Inside a\n`vf-dialog`, `variant=\"default\"` still makes it the dialog's default\nbutton, so Return follows the link.\n\nDisabled — by attribute or an ancestor `<fieldset disabled>` — the `<a>`\nkeeps rendering but loses its `href`, taking `role=\"link\"` and\n`aria-disabled=\"true\"` instead: out of the tab order, and inert to the\npointer and to click, as a disabled `<button>` is.",
|
|
247
|
+
"type": "string | undefined"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"name": "target",
|
|
251
|
+
"description": "With href: where the link opens, as `<a target>`.",
|
|
252
|
+
"type": "string | undefined"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "rel",
|
|
256
|
+
"description": "With href: the link's relationship to this page, as `<a rel>`.",
|
|
257
|
+
"type": "string | undefined"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"name": "download",
|
|
261
|
+
"description": "With href: download the resource instead of navigating to it, as\n`<a download>` — the value, when there is one, names the saved file.",
|
|
262
|
+
"type": "string | undefined"
|
|
263
|
+
},
|
|
224
264
|
{
|
|
225
265
|
"name": "top",
|
|
226
266
|
"description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
|