@timmo001/groundwork-ui 0.9.23 → 0.9.25
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/custom-elements.json +213 -7
- package/package.json +1 -1
- package/src/fields.ts +99 -3
- package/src/gw-input.ts +143 -20
- package/src/gw-option-list.ts +14 -1
- package/src/gw-range.ts +21 -2
- package/src/gw-select.ts +2 -0
package/custom-elements.json
CHANGED
|
@@ -629,7 +629,7 @@
|
|
|
629
629
|
{
|
|
630
630
|
"kind": "variable",
|
|
631
631
|
"name": "fieldStyles",
|
|
632
|
-
"default": "css` :where(gw-input, gw-select, gw-switch, gw-checkbox) { display: flex; flex-direction: column; gap: var(--gw-space-2); font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } :where(gw-input, gw-select) .gw-field-heading { display: flex; gap: var(--gw-space-1); align-items: center; } :where(gw-input, gw-select) .gw-field-heading > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } /* The (i) button by the label that opens a field's longer help */ :where(gw-input, gw-select) .gw-info { display: inline-grid; place-items: center; width: 1.5rem; height: 1.5rem; padding: 0; color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } :where(gw-input, gw-select) .gw-info:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } :where(gw-input, gw-select) .gw-info:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) .gw-info[hidden] { display: none; } :where(gw-input, gw-select) .gw-info > .icon { width: 1rem; height: 1rem; fill: currentColor; } /* Sizes run xs to xl through --gw-field-scale, around 1 for medium, and the control's height follows the theme's density too */ :where(gw-input, gw-select)[size=\"xs\"] { --gw-field-scale: 0.6; } :where(gw-input, gw-select)[size=\"small\"] { --gw-field-scale: 0.8; } :where(gw-input, gw-select)[size=\"large\"] { --gw-field-scale: 1.2; } :where(gw-input, gw-select)[size=\"xl\"] { --gw-field-scale: 1.4; } :where(gw-input, gw-select) :where(input, .gw-select-trigger) { --gw-field-size: var(--gw-field-scale, 1); --gw-icon-size: calc( 1.25rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); width: 100%; min-height: calc( 2.5rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); padding: 0 calc(var(--gw-space-3) * var(--gw-field-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-field-size)); color: var(--gw-color-text-strong); background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-control); transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } /* An icon inside the field, before the text, with the text moved clear of it */ gw-input .gw-input-control { position: relative; display: flex; align-items: center; } gw-input .gw-input-control > .icon { position: absolute; inset-inline-start: calc(var(--gw-space-3) * var(--gw-field-scale, 1)); width: calc(1.125rem * var(--gw-field-scale, 1)); height: calc(1.125rem * var(--gw-field-scale, 1)); color: var(--gw-color-text-secondary); pointer-events: none; fill: currentColor; } gw-input .gw-input-control > .icon[hidden] { display: none; } gw-input .gw-input-control > .icon:not([hidden]) + input { padding-inline-start: calc( (var(--gw-space-3) * 2 + 1.125rem) * var(--gw-field-scale, 1) ); } /* The trigger shows the chosen option, with its icon, and a chevron with room around it that turns over while the list is open */ gw-select .gw-select-trigger { display: flex; gap: calc(var(--gw-space-2) * var(--gw-field-size)); align-items: center; padding-inline-end: calc(var(--gw-space-3) * var(--gw-field-size)); text-align: start; cursor: pointer; } gw-select .gw-select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* With an inline label the select sits among toolbar controls, so it's a pill that fits its content, like the toggles and buttons beside it */ gw-select[inline-label] { display: inline-flex; } gw-select[inline-label] .gw-select-trigger { width: auto; padding-inline: calc(var(--gw-space-4) * var(--gw-field-size)) calc(var(--gw-space-3) * var(--gw-field-size)); border-radius: var(--gw-radius-pill); } /* An inline label reads as a quiet prefix to the value */ gw-select .gw-select-inline-label { flex: none; color: var(--gw-color-text-secondary); } gw-select .gw-select-inline-label[hidden] { display: none; } gw-select .gw-select-value[data-placeholder] { color: var(--gw-color-text-secondary); } gw-select .gw-select-trigger > .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } gw-select .gw-select-trigger > .icon:last-child { color: var(--gw-color-text-secondary); transition: rotate var(--gw-motion-normal) var(--gw-motion-easing); } gw-select[open] .gw-select-trigger > .icon:last-child { rotate: 180deg; } gw-select .gw-select-trigger > .icon[hidden] { display: none; } /* The list opens under the trigger, growing from it, or as a bottom sheet sliding up on phones */ gw-select .gw-select-popup { position: fixed; inset: unset; box-sizing: border-box; max-width: calc(100vw - 12px); padding: 0; margin: 0; overflow: hidden; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-l); opacity: 0; scale: 1 0.96; transform-origin: top; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), scale var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } gw-select .gw-select-popup[data-placement=\"above\"] { transform-origin: bottom; } gw-select .gw-select-popup:popover-open { opacity: 1; scale: 1; } @starting-style { gw-select .gw-select-popup:popover-open { opacity: 0; scale: 1 0.96; } } gw-select .gw-select-popup[data-placement=\"sheet\"] { inset: auto 0 0; width: 100%; max-width: none; padding-block-end: env(safe-area-inset-bottom); border-end-start-radius: 0; border-end-end-radius: 0; scale: 1; translate: 0 100%; --gw-option-list-height: 60vh; } gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 0; } @starting-style { gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 100%; } } gw-select .gw-select-popup[data-placement=\"sheet\"]::backdrop { background: rgb(0 0 0 / 40%); } /* With multiple, a full-width Clear under the list empties the choice */ gw-select .gw-select-clear { display: block; width: 100%; min-height: 2.5rem; padding: 0 var(--gw-space-3); font: inherit; font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-block-start: var(--gw-border-width) solid var(--gw-color-border-quiet); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } gw-select .gw-select-clear:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } gw-select .gw-select-clear:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: calc(var(--gw-focus-width) * -1); } gw-select .gw-select-clear[hidden] { display: none; } :where(gw-input) .gw-input-control:focus-within > .icon { color: var(--gw-color-text-strong); } :where(gw-input, gw-select) :where(input, .gw-select-trigger):hover { border-color: var(--gw-color-border-loud); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :where(input, .gw-select-trigger):focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) [aria-invalid=\"true\"] { border-color: var(--gw-color-border-danger); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :disabled { color: var(--gw-color-text-disabled); cursor: not-allowed; } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-error { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-danger); } :where(gw-switch, gw-checkbox) > label { display: inline-flex; gap: var(--gw-space-3); align-items: center; font-size: var(--gw-font-size-s); cursor: pointer; } :where(gw-input, gw-switch, gw-checkbox, gw-select) .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } gw-checkbox input { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: var(--gw-color-fill-accent-resting); } gw-switch input { position: relative; flex: none; width: 2.5rem; height: 1.5rem; margin: 0; cursor: pointer; appearance: none; background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-loud); border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input::after { position: absolute; inset-block-start: 0.1875rem; inset-inline-start: 0.1875rem; width: 1rem; height: 1rem; content: \"\"; background: var(--gw-color-text-secondary); border-radius: 50%; transition: translate var(--gw-motion-normal) var(--gw-motion-easing), background-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input:checked { background: var(--gw-color-fill-accent-resting); border-color: transparent; } gw-switch input:checked::after { translate: 1rem 0; background: var(--gw-color-on-accent); } gw-switch input:disabled { opacity: 0.5; } gw-range { display: flex; flex-direction: column; gap: var(--gw-space-2); min-width: 0; font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } gw-range > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } gw-range .gw-range-control { position: relative; display: block; } gw-range input { display: block; width: 100%; margin: 0; accent-color: var(--gw-color-fill-accent-resting); cursor: pointer; } gw-range input:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } gw-range input:disabled { cursor: not-allowed; opacity: 0.5; } /* The value above the thumb while it's held */ gw-range .gw-range-bubble { position: absolute; bottom: calc(100% + var(--gw-space-1)); padding: var(--gw-space-1) var(--gw-space-2); font-size: var(--gw-font-size-xs); font-variant-numeric: tabular-nums; color: var(--gw-color-on-accent); white-space: nowrap; pointer-events: none; background: var(--gw-color-fill-accent-resting); border-radius: var(--gw-radius-control); box-shadow: var(--gw-shadow-s); translate: -50% 0; } gw-range .gw-range-bubble[hidden] { display: none; } gw-range .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } gw-range .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } `",
|
|
632
|
+
"default": "css` :where(gw-input, gw-select, gw-switch, gw-checkbox) { display: flex; flex-direction: column; gap: var(--gw-space-2); font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } :where(gw-input, gw-select) .gw-field-heading { display: flex; gap: var(--gw-space-1); align-items: center; } :where(gw-input, gw-select) .gw-field-heading > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } /* The (i) button by the label that opens a field's longer help */ :where(gw-input, gw-select) .gw-info { display: inline-grid; place-items: center; width: 1.5rem; height: 1.5rem; padding: 0; color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } :where(gw-input, gw-select) .gw-info:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } :where(gw-input, gw-select) .gw-info:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) .gw-info[hidden] { display: none; } :where(gw-input, gw-select) .gw-info > .icon { width: 1rem; height: 1rem; fill: currentColor; } /* Sizes run xs to xl through --gw-field-scale, around 1 for medium, and the control's height follows the theme's density too */ :where(gw-input, gw-select)[size=\"xs\"] { --gw-field-scale: 0.6; } :where(gw-input, gw-select)[size=\"small\"] { --gw-field-scale: 0.8; } :where(gw-input, gw-select)[size=\"large\"] { --gw-field-scale: 1.2; } :where(gw-input, gw-select)[size=\"xl\"] { --gw-field-scale: 1.4; } :where(gw-input, gw-select) :where(input, textarea, .gw-select-trigger) { --gw-field-size: var(--gw-field-scale, 1); --gw-icon-size: calc( 1.25rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); width: 100%; min-height: calc( 2.5rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); padding: 0 calc(var(--gw-space-3) * var(--gw-field-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-field-size)); color: var(--gw-color-text-strong); background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-control); transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } /* An icon inside the field, before the text, with the text moved clear of it */ gw-input .gw-input-control { position: relative; display: flex; align-items: center; } gw-input .gw-input-control > .icon { position: absolute; inset-inline-start: calc(var(--gw-space-3) * var(--gw-field-scale, 1)); width: calc(1.125rem * var(--gw-field-scale, 1)); height: calc(1.125rem * var(--gw-field-scale, 1)); color: var(--gw-color-text-secondary); pointer-events: none; fill: currentColor; } gw-input .gw-input-control > .icon[hidden] { display: none; } gw-input .gw-input-control > .icon:not([hidden]) + input { padding-inline-start: calc( (var(--gw-space-3) * 2 + 1.125rem) * var(--gw-field-scale, 1) ); } /* A textarea keeps the field's padding on every side and grows by its corner, never sideways out of the layout */ gw-input textarea { display: block; padding-block: calc(var(--gw-space-2) * var(--gw-field-size)); line-height: 1.5; resize: vertical; } gw-input[monospace] :where(input, textarea) { font-family: var(--gw-font-mono); } gw-input .gw-optional { font-weight: var(--gw-font-weight-regular); color: var(--gw-color-text-secondary); } gw-input .gw-optional[hidden] { display: none; } /* The show and hide button sits inside the end of a password field, with the text kept clear of it */ gw-input .gw-reveal { position: absolute; inset-inline-end: calc(var(--gw-space-1) * var(--gw-field-scale, 1)); display: grid; place-items: center; width: calc(2rem * var(--gw-field-scale, 1)); height: calc(2rem * var(--gw-field-scale, 1)); padding: 0; color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-control); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } gw-input .gw-reveal:hover:not(:disabled) { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } gw-input .gw-reveal:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: calc(var(--gw-focus-width) * -1); } gw-input .gw-reveal:disabled { cursor: not-allowed; opacity: 0.5; } gw-input .gw-reveal[hidden] { display: none; } gw-input .gw-reveal > .icon { width: calc(1.125rem * var(--gw-field-scale, 1)); height: calc(1.125rem * var(--gw-field-scale, 1)); fill: currentColor; } gw-input .gw-input-control:has(> .gw-reveal:not([hidden])) > input { padding-inline-end: calc( (var(--gw-space-1) + 2rem + var(--gw-space-1)) * var(--gw-field-scale, 1) ); } /* The trigger shows the chosen option, with its icon, and a chevron with room around it that turns over while the list is open */ gw-select .gw-select-trigger { display: flex; gap: calc(var(--gw-space-2) * var(--gw-field-size)); align-items: center; padding-inline-end: calc(var(--gw-space-3) * var(--gw-field-size)); text-align: start; cursor: pointer; } gw-select .gw-select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* With an inline label the select sits among toolbar controls, so it's a pill that fits its content, like the toggles and buttons beside it */ gw-select[inline-label] { display: inline-flex; } gw-select[inline-label] .gw-select-trigger { width: auto; padding-inline: calc(var(--gw-space-4) * var(--gw-field-size)) calc(var(--gw-space-3) * var(--gw-field-size)); border-radius: var(--gw-radius-pill); } /* An inline label reads as a quiet prefix to the value */ gw-select .gw-select-inline-label { flex: none; color: var(--gw-color-text-secondary); } gw-select .gw-select-inline-label[hidden] { display: none; } gw-select .gw-select-value[data-placeholder] { color: var(--gw-color-text-secondary); } gw-select .gw-select-trigger > .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } gw-select .gw-select-trigger > .icon:last-child { color: var(--gw-color-text-secondary); transition: rotate var(--gw-motion-normal) var(--gw-motion-easing); } gw-select[open] .gw-select-trigger > .icon:last-child { rotate: 180deg; } gw-select .gw-select-trigger > .icon[hidden] { display: none; } /* The list opens under the trigger, growing from it, or as a bottom sheet sliding up on phones */ gw-select .gw-select-popup { position: fixed; inset: unset; box-sizing: border-box; max-width: calc(100vw - 12px); padding: 0; margin: 0; overflow: hidden; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-l); opacity: 0; scale: 1 0.96; transform-origin: top; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), scale var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } gw-select .gw-select-popup[data-placement=\"above\"] { transform-origin: bottom; } gw-select .gw-select-popup:popover-open { opacity: 1; scale: 1; } @starting-style { gw-select .gw-select-popup:popover-open { opacity: 0; scale: 1 0.96; } } gw-select .gw-select-popup[data-placement=\"sheet\"] { inset: auto 0 0; width: 100%; max-width: none; padding-block-end: env(safe-area-inset-bottom); border-end-start-radius: 0; border-end-end-radius: 0; scale: 1; translate: 0 100%; --gw-option-list-height: 60vh; } gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 0; } @starting-style { gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 100%; } } gw-select .gw-select-popup[data-placement=\"sheet\"]::backdrop { background: rgb(0 0 0 / 40%); } /* With multiple, a full-width Clear under the list empties the choice */ gw-select .gw-select-clear { display: block; width: 100%; min-height: 2.5rem; padding: 0 var(--gw-space-3); font: inherit; font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-block-start: var(--gw-border-width) solid var(--gw-color-border-quiet); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } gw-select .gw-select-clear:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } gw-select .gw-select-clear:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: calc(var(--gw-focus-width) * -1); } gw-select .gw-select-clear[hidden] { display: none; } :where(gw-input) .gw-input-control:focus-within > .icon { color: var(--gw-color-text-strong); } :where(gw-input, gw-select) :where(input, textarea, .gw-select-trigger):hover { border-color: var(--gw-color-border-loud); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :where(input, textarea, .gw-select-trigger):focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) [aria-invalid=\"true\"] { border-color: var(--gw-color-border-danger); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :disabled { color: var(--gw-color-text-disabled); cursor: not-allowed; } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-error { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-danger); } :where(gw-switch, gw-checkbox) > label { display: inline-flex; gap: var(--gw-space-3); align-items: center; font-size: var(--gw-font-size-s); cursor: pointer; } :where(gw-input, gw-switch, gw-checkbox, gw-select) .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } gw-checkbox input { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: var(--gw-color-fill-accent-resting); } gw-switch input { position: relative; flex: none; width: 2.5rem; height: 1.5rem; margin: 0; cursor: pointer; appearance: none; background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-loud); border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input::after { position: absolute; inset-block-start: 0.1875rem; inset-inline-start: 0.1875rem; width: 1rem; height: 1rem; content: \"\"; background: var(--gw-color-text-secondary); border-radius: 50%; transition: translate var(--gw-motion-normal) var(--gw-motion-easing), background-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input:checked { background: var(--gw-color-fill-accent-resting); border-color: transparent; } gw-switch input:checked::after { translate: 1rem 0; background: var(--gw-color-on-accent); } gw-switch input:disabled { opacity: 0.5; } gw-range { display: flex; flex-direction: column; gap: var(--gw-space-2); min-width: 0; font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } gw-range > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } gw-range .gw-range-row { display: flex; gap: var(--gw-space-3); align-items: center; } gw-range .gw-range-control { position: relative; display: block; flex: 1; min-width: 0; } /* The value beside the track, with readout */ gw-range .gw-range-readout { min-width: 5ch; font-size: var(--gw-font-size-s); font-variant-numeric: tabular-nums; color: var(--gw-color-text-secondary); text-align: end; white-space: nowrap; } gw-range .gw-range-readout[hidden] { display: none; } gw-range input { display: block; width: 100%; margin: 0; accent-color: var(--gw-color-fill-accent-resting); cursor: pointer; } gw-range input:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } gw-range input:disabled { cursor: not-allowed; opacity: 0.5; } /* The value above the thumb while it's held */ gw-range .gw-range-bubble { position: absolute; bottom: calc(100% + var(--gw-space-1)); padding: var(--gw-space-1) var(--gw-space-2); font-size: var(--gw-font-size-xs); font-variant-numeric: tabular-nums; color: var(--gw-color-on-accent); white-space: nowrap; pointer-events: none; background: var(--gw-color-fill-accent-resting); border-radius: var(--gw-radius-control); box-shadow: var(--gw-shadow-s); translate: -50% 0; } gw-range .gw-range-bubble[hidden] { display: none; } gw-range .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } gw-range .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } `",
|
|
633
633
|
"description": "Field components render their native control in light DOM, never in a\nshadow root: password managers and `<label for>` can't reach across one,\nand a form only submits controls it can see. Their styles are adopted into\nwhichever document or shadow root they sit in."
|
|
634
634
|
},
|
|
635
635
|
{
|
|
@@ -9837,7 +9837,7 @@
|
|
|
9837
9837
|
"declarations": [
|
|
9838
9838
|
{
|
|
9839
9839
|
"kind": "class",
|
|
9840
|
-
"description": "A labeled native `<input>`, with an
|
|
9840
|
+
"description": "A labeled native `<input>`, or a `<textarea>` with `multiline`, with an\noptional hint and error. Enter submits its form when the form's submit\nbutton is a `gw-button`, as it would with a native one.",
|
|
9841
9841
|
"name": "GwInput",
|
|
9842
9842
|
"cssProperties": [
|
|
9843
9843
|
{
|
|
@@ -9857,7 +9857,7 @@
|
|
|
9857
9857
|
"text": "Properties"
|
|
9858
9858
|
},
|
|
9859
9859
|
"static": true,
|
|
9860
|
-
"default": "{ label: {}, name: {}, type: {}, value: {}, placeholder: {}, autocomplete: {}, hint: {}, info: {}, error: {}, required: { type: \"boolean\" }, disabled: { type: \"boolean\" }, size: { reflect: true }, icon: {}, hideLabel: { type: \"boolean\", attribute: \"hide-label\", reflect: true }, }",
|
|
9860
|
+
"default": "{ label: {}, name: {}, type: {}, value: {}, placeholder: {}, autocomplete: {}, inputmode: {}, min: {}, max: {}, step: {}, hint: {}, info: {}, error: {}, required: { type: \"boolean\" }, optional: { type: \"boolean\" }, disabled: { type: \"boolean\" }, multiline: { type: \"boolean\", reflect: true }, rows: { type: \"number\" }, monospace: { type: \"boolean\", reflect: true }, reveal: { type: \"boolean\" }, size: { reflect: true }, icon: {}, hideLabel: { type: \"boolean\", attribute: \"hide-label\", reflect: true }, }",
|
|
9861
9861
|
"inheritedFrom": {
|
|
9862
9862
|
"name": "GwElement",
|
|
9863
9863
|
"module": "src/element.ts"
|
|
@@ -9911,6 +9911,42 @@
|
|
|
9911
9911
|
},
|
|
9912
9912
|
"default": "\"\""
|
|
9913
9913
|
},
|
|
9914
|
+
{
|
|
9915
|
+
"kind": "field",
|
|
9916
|
+
"name": "inputmode",
|
|
9917
|
+
"type": {
|
|
9918
|
+
"text": "string"
|
|
9919
|
+
},
|
|
9920
|
+
"description": "The native inputmode hint, which picks the on-screen keyboard.",
|
|
9921
|
+
"default": "\"\""
|
|
9922
|
+
},
|
|
9923
|
+
{
|
|
9924
|
+
"kind": "field",
|
|
9925
|
+
"name": "min",
|
|
9926
|
+
"type": {
|
|
9927
|
+
"text": "string"
|
|
9928
|
+
},
|
|
9929
|
+
"description": "The lowest value of a number, date or time field.",
|
|
9930
|
+
"default": "\"\""
|
|
9931
|
+
},
|
|
9932
|
+
{
|
|
9933
|
+
"kind": "field",
|
|
9934
|
+
"name": "max",
|
|
9935
|
+
"type": {
|
|
9936
|
+
"text": "string"
|
|
9937
|
+
},
|
|
9938
|
+
"description": "The highest value of a number, date or time field.",
|
|
9939
|
+
"default": "\"\""
|
|
9940
|
+
},
|
|
9941
|
+
{
|
|
9942
|
+
"kind": "field",
|
|
9943
|
+
"name": "step",
|
|
9944
|
+
"type": {
|
|
9945
|
+
"text": "string"
|
|
9946
|
+
},
|
|
9947
|
+
"description": "The step between values of a number, date or time field.",
|
|
9948
|
+
"default": "\"\""
|
|
9949
|
+
},
|
|
9914
9950
|
{
|
|
9915
9951
|
"kind": "field",
|
|
9916
9952
|
"name": "hint",
|
|
@@ -9944,6 +9980,15 @@
|
|
|
9944
9980
|
},
|
|
9945
9981
|
"default": "false"
|
|
9946
9982
|
},
|
|
9983
|
+
{
|
|
9984
|
+
"kind": "field",
|
|
9985
|
+
"name": "optional",
|
|
9986
|
+
"type": {
|
|
9987
|
+
"text": "boolean"
|
|
9988
|
+
},
|
|
9989
|
+
"description": "Adds \"(optional)\" after the label.",
|
|
9990
|
+
"default": "false"
|
|
9991
|
+
},
|
|
9947
9992
|
{
|
|
9948
9993
|
"kind": "field",
|
|
9949
9994
|
"name": "disabled",
|
|
@@ -9952,6 +9997,42 @@
|
|
|
9952
9997
|
},
|
|
9953
9998
|
"default": "false"
|
|
9954
9999
|
},
|
|
10000
|
+
{
|
|
10001
|
+
"kind": "field",
|
|
10002
|
+
"name": "multiline",
|
|
10003
|
+
"type": {
|
|
10004
|
+
"text": "boolean"
|
|
10005
|
+
},
|
|
10006
|
+
"description": "Uses a `<textarea>` instead of an `<input>`.",
|
|
10007
|
+
"default": "false"
|
|
10008
|
+
},
|
|
10009
|
+
{
|
|
10010
|
+
"kind": "field",
|
|
10011
|
+
"name": "rows",
|
|
10012
|
+
"type": {
|
|
10013
|
+
"text": "number"
|
|
10014
|
+
},
|
|
10015
|
+
"description": "The textarea's starting height in lines.",
|
|
10016
|
+
"default": "3"
|
|
10017
|
+
},
|
|
10018
|
+
{
|
|
10019
|
+
"kind": "field",
|
|
10020
|
+
"name": "monospace",
|
|
10021
|
+
"type": {
|
|
10022
|
+
"text": "boolean"
|
|
10023
|
+
},
|
|
10024
|
+
"description": "Sets the text in the monospace font and turns off spellcheck.",
|
|
10025
|
+
"default": "false"
|
|
10026
|
+
},
|
|
10027
|
+
{
|
|
10028
|
+
"kind": "field",
|
|
10029
|
+
"name": "reveal",
|
|
10030
|
+
"type": {
|
|
10031
|
+
"text": "boolean"
|
|
10032
|
+
},
|
|
10033
|
+
"description": "On a password field, adds a button to show and hide what's typed.",
|
|
10034
|
+
"default": "false"
|
|
10035
|
+
},
|
|
9955
10036
|
{
|
|
9956
10037
|
"kind": "field",
|
|
9957
10038
|
"name": "size",
|
|
@@ -10009,6 +10090,39 @@
|
|
|
10009
10090
|
"privacy": "private",
|
|
10010
10091
|
"readonly": true
|
|
10011
10092
|
},
|
|
10093
|
+
{
|
|
10094
|
+
"kind": "field",
|
|
10095
|
+
"name": "#textarea",
|
|
10096
|
+
"privacy": "private",
|
|
10097
|
+
"readonly": true
|
|
10098
|
+
},
|
|
10099
|
+
{
|
|
10100
|
+
"kind": "field",
|
|
10101
|
+
"name": "#optional",
|
|
10102
|
+
"privacy": "private",
|
|
10103
|
+
"readonly": true
|
|
10104
|
+
},
|
|
10105
|
+
{
|
|
10106
|
+
"kind": "field",
|
|
10107
|
+
"name": "#revealIcon",
|
|
10108
|
+
"privacy": "private",
|
|
10109
|
+
"readonly": true
|
|
10110
|
+
},
|
|
10111
|
+
{
|
|
10112
|
+
"kind": "field",
|
|
10113
|
+
"name": "#reveal",
|
|
10114
|
+
"privacy": "private",
|
|
10115
|
+
"readonly": true
|
|
10116
|
+
},
|
|
10117
|
+
{
|
|
10118
|
+
"kind": "field",
|
|
10119
|
+
"name": "#revealed",
|
|
10120
|
+
"privacy": "private",
|
|
10121
|
+
"type": {
|
|
10122
|
+
"text": "boolean"
|
|
10123
|
+
},
|
|
10124
|
+
"default": "false"
|
|
10125
|
+
},
|
|
10012
10126
|
{
|
|
10013
10127
|
"kind": "field",
|
|
10014
10128
|
"name": "#hint",
|
|
@@ -10021,6 +10135,13 @@
|
|
|
10021
10135
|
"privacy": "private",
|
|
10022
10136
|
"readonly": true
|
|
10023
10137
|
},
|
|
10138
|
+
{
|
|
10139
|
+
"kind": "field",
|
|
10140
|
+
"name": "#control",
|
|
10141
|
+
"privacy": "private",
|
|
10142
|
+
"description": "The native control in use: the textarea when multiline, else the input.",
|
|
10143
|
+
"readonly": true
|
|
10144
|
+
},
|
|
10024
10145
|
{
|
|
10025
10146
|
"kind": "method",
|
|
10026
10147
|
"name": "create",
|
|
@@ -10389,6 +10510,34 @@
|
|
|
10389
10510
|
"description": "The native autocomplete hint.",
|
|
10390
10511
|
"name": "autocomplete"
|
|
10391
10512
|
},
|
|
10513
|
+
{
|
|
10514
|
+
"type": {
|
|
10515
|
+
"text": "string"
|
|
10516
|
+
},
|
|
10517
|
+
"description": "The native inputmode hint, such as `numeric` for a port, which picks the on-screen keyboard.",
|
|
10518
|
+
"name": "inputmode"
|
|
10519
|
+
},
|
|
10520
|
+
{
|
|
10521
|
+
"type": {
|
|
10522
|
+
"text": "string"
|
|
10523
|
+
},
|
|
10524
|
+
"description": "The lowest value of a `number`, date or time field.",
|
|
10525
|
+
"name": "min"
|
|
10526
|
+
},
|
|
10527
|
+
{
|
|
10528
|
+
"type": {
|
|
10529
|
+
"text": "string"
|
|
10530
|
+
},
|
|
10531
|
+
"description": "The highest value of a `number`, date or time field.",
|
|
10532
|
+
"name": "max"
|
|
10533
|
+
},
|
|
10534
|
+
{
|
|
10535
|
+
"type": {
|
|
10536
|
+
"text": "string"
|
|
10537
|
+
},
|
|
10538
|
+
"description": "The step between values of a `number`, date or time field, or `any`.",
|
|
10539
|
+
"name": "step"
|
|
10540
|
+
},
|
|
10392
10541
|
{
|
|
10393
10542
|
"type": {
|
|
10394
10543
|
"text": "string"
|
|
@@ -10407,7 +10556,7 @@
|
|
|
10407
10556
|
"type": {
|
|
10408
10557
|
"text": "string"
|
|
10409
10558
|
},
|
|
10410
|
-
"description": "An error under the field, which marks it invalid.",
|
|
10559
|
+
"description": "An error under the field, which marks it invalid and is announced.",
|
|
10411
10560
|
"name": "error"
|
|
10412
10561
|
},
|
|
10413
10562
|
{
|
|
@@ -10417,6 +10566,13 @@
|
|
|
10417
10566
|
"description": "Requires a value.",
|
|
10418
10567
|
"name": "required"
|
|
10419
10568
|
},
|
|
10569
|
+
{
|
|
10570
|
+
"type": {
|
|
10571
|
+
"text": "boolean"
|
|
10572
|
+
},
|
|
10573
|
+
"description": "Adds \"(optional)\" after the label, for the few optional fields in a form that mostly requires them.",
|
|
10574
|
+
"name": "optional"
|
|
10575
|
+
},
|
|
10420
10576
|
{
|
|
10421
10577
|
"type": {
|
|
10422
10578
|
"text": "boolean"
|
|
@@ -10424,6 +10580,34 @@
|
|
|
10424
10580
|
"description": "Disables the field.",
|
|
10425
10581
|
"name": "disabled"
|
|
10426
10582
|
},
|
|
10583
|
+
{
|
|
10584
|
+
"type": {
|
|
10585
|
+
"text": "boolean"
|
|
10586
|
+
},
|
|
10587
|
+
"description": "Uses a `<textarea>` that grows by dragging its corner, for text that runs over lines.",
|
|
10588
|
+
"name": "multiline"
|
|
10589
|
+
},
|
|
10590
|
+
{
|
|
10591
|
+
"type": {
|
|
10592
|
+
"text": "number"
|
|
10593
|
+
},
|
|
10594
|
+
"description": "The textarea's starting height in lines. Defaults to 3.",
|
|
10595
|
+
"name": "rows"
|
|
10596
|
+
},
|
|
10597
|
+
{
|
|
10598
|
+
"type": {
|
|
10599
|
+
"text": "boolean"
|
|
10600
|
+
},
|
|
10601
|
+
"description": "Sets the text in the monospace font, for paths, commands, tokens and other machine text. It also turns off spellcheck.",
|
|
10602
|
+
"name": "monospace"
|
|
10603
|
+
},
|
|
10604
|
+
{
|
|
10605
|
+
"type": {
|
|
10606
|
+
"text": "boolean"
|
|
10607
|
+
},
|
|
10608
|
+
"description": "On a password field, adds a button at the end to show and hide what's typed.",
|
|
10609
|
+
"name": "reveal"
|
|
10610
|
+
},
|
|
10427
10611
|
{
|
|
10428
10612
|
"type": {
|
|
10429
10613
|
"text": "\"xs\"|\"small\"|\"medium\"|\"large\"|\"xl\""
|
|
@@ -12865,7 +13049,7 @@
|
|
|
12865
13049
|
"text": "readonly CSSStyleSheet[]"
|
|
12866
13050
|
},
|
|
12867
13051
|
"static": true,
|
|
12868
|
-
"default": "[ base, css` :host { display: flex; flex-direction: column; min-height: 0; } .search { display: flex; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-1) var(--gw-space-3); color: var(--gw-color-text-secondary); border-block-end: var(--gw-border-width) solid var(--gw-color-border-quiet); } :host(:not([search])) .search { display: none; } input { flex: 1; min-width: 0; height: 2.5rem; padding: 0; font: inherit; font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); background: none; border: 0; outline: none; } /* The search field is always focused while the list is open, so the row's divider marks it rather than a ring */ input:focus-visible { outline: none; } input::placeholder { color: var(--gw-color-text-secondary); } [role=\"listbox\"] { position: relative; max-height: var(--gw-option-list-height, 20rem); overflow-y: auto; overscroll-behavior: contain; outline: none; } .spacer { position: relative; } .row { position: absolute; inset-inline: ${listPadding}px; box-sizing: border-box; display: flex; gap: var(--gw-space-3); align-items: center; padding: 0 var(--gw-space-2); } .group { align-items: flex-end; padding-block-end: var(--gw-space-1); font-size: var(--gw-font-size-xs); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); } [role=\"option\"] { font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); cursor: pointer; border-radius: var(--gw-radius-control); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } /* Mixed from the text color, so the active row stands out on any surface the list sits on, overlays included */ [role=\"option\"][data-active] { background: color-mix( in oklab, transparent, var(--gw-color-text-primary) 12% ); } [role=\"option\"][aria-selected=\"true\"] { color: var(--gw-color-text-strong); } [role=\"option\"] > .icon { flex: none; color: var(--gw-color-text-primary); } [role=\"option\"] > .check { color: var(--gw-color-accent); } .text { display: flex; flex: 1; flex-direction: column; min-width: 0; } .text span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .description { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } .swatches { display: flex; flex: none; } .swatches span, .swatch { width: 0.875rem; height: 0.875rem; margin-inline-start: -0.25rem; border: 1px solid color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent); border-radius: 50%; } .swatch { flex: none; width: 0.625rem; height: 0.625rem; margin-inline: 0.125rem; } .none { padding: var(--gw-space-4); font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); text-align: center; } .none[hidden] { display: none; } `, ]",
|
|
13052
|
+
"default": "[ base, css` :host { display: flex; flex-direction: column; min-height: 0; } .search { display: flex; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-1) var(--gw-space-3); color: var(--gw-color-text-secondary); border-block-end: var(--gw-border-width) solid var(--gw-color-border-quiet); } :host(:not([search])) .search { display: none; } input { flex: 1; min-width: 0; height: 2.5rem; padding: 0; font: inherit; font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); background: none; border: 0; outline: none; } /* The search field is always focused while the list is open, so the row's divider marks it rather than a ring */ input:focus-visible { outline: none; } input::placeholder { color: var(--gw-color-text-secondary); } [role=\"listbox\"] { position: relative; max-height: var(--gw-option-list-height, 20rem); overflow-y: auto; overscroll-behavior: contain; outline: none; } .spacer { position: relative; } .row { position: absolute; inset-inline: ${listPadding}px; box-sizing: border-box; display: flex; gap: var(--gw-space-3); align-items: center; padding: 0 var(--gw-space-2); } .group { align-items: flex-end; padding-block-end: var(--gw-space-1); font-size: var(--gw-font-size-xs); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); } [role=\"option\"] { font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); cursor: pointer; border-radius: var(--gw-radius-control); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } /* Mixed from the text color, so the active row stands out on any surface the list sits on, overlays included */ [role=\"option\"][data-active] { background: color-mix( in oklab, transparent, var(--gw-color-text-primary) 12% ); } [role=\"option\"][aria-selected=\"true\"] { color: var(--gw-color-text-strong); } /* A disabled option can still be reached, so its reason is read, but reads as unavailable and can't be picked */ [role=\"option\"][aria-disabled=\"true\"] { color: var(--gw-color-text-disabled); cursor: not-allowed; } [role=\"option\"] > .icon { flex: none; color: var(--gw-color-text-primary); } [role=\"option\"] > .check { color: var(--gw-color-accent); } .text { display: flex; flex: 1; flex-direction: column; min-width: 0; } .text span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .description { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } .swatches { display: flex; flex: none; } .swatches span, .swatch { width: 0.875rem; height: 0.875rem; margin-inline-start: -0.25rem; border: 1px solid color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent); border-radius: 50%; } .swatch { flex: none; width: 0.625rem; height: 0.625rem; margin-inline: 0.125rem; } .none { padding: var(--gw-space-4); font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); text-align: center; } .none[hidden] { display: none; } `, ]",
|
|
12869
13053
|
"inheritedFrom": {
|
|
12870
13054
|
"name": "GwElement",
|
|
12871
13055
|
"module": "src/element.ts"
|
|
@@ -14614,7 +14798,7 @@
|
|
|
14614
14798
|
"text": "Properties"
|
|
14615
14799
|
},
|
|
14616
14800
|
"static": true,
|
|
14617
|
-
"default": "{ label: {}, labelHidden: { type: \"boolean\", attribute: \"label-hidden\" }, name: {}, value: { type: \"number\" }, min: { type: \"number\" }, max: { type: \"number\" }, step: { type: \"number\" }, unit: {}, hint: {}, disabled: { type: \"boolean\" }, }",
|
|
14801
|
+
"default": "{ label: {}, labelHidden: { type: \"boolean\", attribute: \"label-hidden\" }, name: {}, value: { type: \"number\" }, min: { type: \"number\" }, max: { type: \"number\" }, step: { type: \"number\" }, unit: {}, readout: { type: \"boolean\" }, hint: {}, disabled: { type: \"boolean\" }, }",
|
|
14618
14802
|
"inheritedFrom": {
|
|
14619
14803
|
"name": "GwElement",
|
|
14620
14804
|
"module": "src/element.ts"
|
|
@@ -14686,6 +14870,15 @@
|
|
|
14686
14870
|
"description": "Shown after the value, such as `%`.",
|
|
14687
14871
|
"default": "\"\""
|
|
14688
14872
|
},
|
|
14873
|
+
{
|
|
14874
|
+
"kind": "field",
|
|
14875
|
+
"name": "readout",
|
|
14876
|
+
"type": {
|
|
14877
|
+
"text": "boolean"
|
|
14878
|
+
},
|
|
14879
|
+
"description": "Shows the value beside the track.",
|
|
14880
|
+
"default": "false"
|
|
14881
|
+
},
|
|
14689
14882
|
{
|
|
14690
14883
|
"kind": "field",
|
|
14691
14884
|
"name": "hint",
|
|
@@ -14726,6 +14919,12 @@
|
|
|
14726
14919
|
"privacy": "private",
|
|
14727
14920
|
"readonly": true
|
|
14728
14921
|
},
|
|
14922
|
+
{
|
|
14923
|
+
"kind": "field",
|
|
14924
|
+
"name": "#readout",
|
|
14925
|
+
"privacy": "private",
|
|
14926
|
+
"readonly": true
|
|
14927
|
+
},
|
|
14729
14928
|
{
|
|
14730
14929
|
"kind": "field",
|
|
14731
14930
|
"name": "#hint",
|
|
@@ -15140,9 +15339,16 @@
|
|
|
15140
15339
|
"type": {
|
|
15141
15340
|
"text": "string"
|
|
15142
15341
|
},
|
|
15143
|
-
"description": "Shown after the value in the bubble and for assistive technology, such as `%`. Empty for a plain number.",
|
|
15342
|
+
"description": "Shown after the value in the bubble, the readout and for assistive technology, such as `%`. Empty for a plain number.",
|
|
15144
15343
|
"name": "unit"
|
|
15145
15344
|
},
|
|
15345
|
+
{
|
|
15346
|
+
"type": {
|
|
15347
|
+
"text": "boolean"
|
|
15348
|
+
},
|
|
15349
|
+
"description": "Shows the value beside the track, kept up to date while it's dragged.",
|
|
15350
|
+
"name": "readout"
|
|
15351
|
+
},
|
|
15146
15352
|
{
|
|
15147
15353
|
"type": {
|
|
15148
15354
|
"text": "string"
|
package/package.json
CHANGED
package/src/fields.ts
CHANGED
|
@@ -88,7 +88,7 @@ export const fieldStyles = css`
|
|
|
88
88
|
--gw-field-scale: 1.4;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
:where(gw-input, gw-select) :where(input, .gw-select-trigger) {
|
|
91
|
+
:where(gw-input, gw-select) :where(input, textarea, .gw-select-trigger) {
|
|
92
92
|
--gw-field-size: var(--gw-field-scale, 1);
|
|
93
93
|
--gw-icon-size: calc(
|
|
94
94
|
1.25rem * var(--gw-field-size) * var(--gw-control-scale, 1)
|
|
@@ -136,6 +136,79 @@ export const fieldStyles = css`
|
|
|
136
136
|
);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/* A textarea keeps the field's padding on every side and grows by its
|
|
140
|
+
corner, never sideways out of the layout */
|
|
141
|
+
gw-input textarea {
|
|
142
|
+
display: block;
|
|
143
|
+
padding-block: calc(var(--gw-space-2) * var(--gw-field-size));
|
|
144
|
+
line-height: 1.5;
|
|
145
|
+
resize: vertical;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
gw-input[monospace] :where(input, textarea) {
|
|
149
|
+
font-family: var(--gw-font-mono);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
gw-input .gw-optional {
|
|
153
|
+
font-weight: var(--gw-font-weight-regular);
|
|
154
|
+
color: var(--gw-color-text-secondary);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
gw-input .gw-optional[hidden] {
|
|
158
|
+
display: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* The show and hide button sits inside the end of a password field, with
|
|
162
|
+
the text kept clear of it */
|
|
163
|
+
gw-input .gw-reveal {
|
|
164
|
+
position: absolute;
|
|
165
|
+
inset-inline-end: calc(var(--gw-space-1) * var(--gw-field-scale, 1));
|
|
166
|
+
display: grid;
|
|
167
|
+
place-items: center;
|
|
168
|
+
width: calc(2rem * var(--gw-field-scale, 1));
|
|
169
|
+
height: calc(2rem * var(--gw-field-scale, 1));
|
|
170
|
+
padding: 0;
|
|
171
|
+
color: var(--gw-color-text-secondary);
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
background: none;
|
|
174
|
+
border: 0;
|
|
175
|
+
border-radius: var(--gw-radius-control);
|
|
176
|
+
transition:
|
|
177
|
+
color var(--gw-motion-fast) var(--gw-motion-easing),
|
|
178
|
+
background-color var(--gw-motion-fast) var(--gw-motion-easing);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
gw-input .gw-reveal:hover:not(:disabled) {
|
|
182
|
+
color: var(--gw-color-text-strong);
|
|
183
|
+
background: var(--gw-color-fill-neutral-hovered);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
gw-input .gw-reveal:focus-visible {
|
|
187
|
+
outline: var(--gw-focus-width) solid var(--gw-color-focus);
|
|
188
|
+
outline-offset: calc(var(--gw-focus-width) * -1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
gw-input .gw-reveal:disabled {
|
|
192
|
+
cursor: not-allowed;
|
|
193
|
+
opacity: 0.5;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
gw-input .gw-reveal[hidden] {
|
|
197
|
+
display: none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
gw-input .gw-reveal > .icon {
|
|
201
|
+
width: calc(1.125rem * var(--gw-field-scale, 1));
|
|
202
|
+
height: calc(1.125rem * var(--gw-field-scale, 1));
|
|
203
|
+
fill: currentColor;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
gw-input .gw-input-control:has(> .gw-reveal:not([hidden])) > input {
|
|
207
|
+
padding-inline-end: calc(
|
|
208
|
+
(var(--gw-space-1) + 2rem + var(--gw-space-1)) * var(--gw-field-scale, 1)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
139
212
|
/* The trigger shows the chosen option, with its icon, and a chevron with
|
|
140
213
|
room around it that turns over while the list is open */
|
|
141
214
|
gw-select .gw-select-trigger {
|
|
@@ -308,12 +381,13 @@ export const fieldStyles = css`
|
|
|
308
381
|
color: var(--gw-color-text-strong);
|
|
309
382
|
}
|
|
310
383
|
|
|
311
|
-
:where(gw-input, gw-select)
|
|
384
|
+
:where(gw-input, gw-select)
|
|
385
|
+
:where(input, textarea, .gw-select-trigger):hover {
|
|
312
386
|
border-color: var(--gw-color-border-loud);
|
|
313
387
|
}
|
|
314
388
|
|
|
315
389
|
:where(gw-input, gw-select, gw-switch, gw-checkbox)
|
|
316
|
-
:where(input, .gw-select-trigger):focus-visible {
|
|
390
|
+
:where(input, textarea, .gw-select-trigger):focus-visible {
|
|
317
391
|
outline: var(--gw-focus-width) solid var(--gw-color-focus);
|
|
318
392
|
outline-offset: var(--gw-focus-offset);
|
|
319
393
|
}
|
|
@@ -422,9 +496,31 @@ export const fieldStyles = css`
|
|
|
422
496
|
color: var(--gw-color-text-strong);
|
|
423
497
|
}
|
|
424
498
|
|
|
499
|
+
gw-range .gw-range-row {
|
|
500
|
+
display: flex;
|
|
501
|
+
gap: var(--gw-space-3);
|
|
502
|
+
align-items: center;
|
|
503
|
+
}
|
|
504
|
+
|
|
425
505
|
gw-range .gw-range-control {
|
|
426
506
|
position: relative;
|
|
427
507
|
display: block;
|
|
508
|
+
flex: 1;
|
|
509
|
+
min-width: 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/* The value beside the track, with readout */
|
|
513
|
+
gw-range .gw-range-readout {
|
|
514
|
+
min-width: 5ch;
|
|
515
|
+
font-size: var(--gw-font-size-s);
|
|
516
|
+
font-variant-numeric: tabular-nums;
|
|
517
|
+
color: var(--gw-color-text-secondary);
|
|
518
|
+
text-align: end;
|
|
519
|
+
white-space: nowrap;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
gw-range .gw-range-readout[hidden] {
|
|
523
|
+
display: none;
|
|
428
524
|
}
|
|
429
525
|
|
|
430
526
|
gw-range input {
|
package/src/gw-input.ts
CHANGED
|
@@ -9,10 +9,17 @@ import { createIcon, setIconPath } from "./styles.js";
|
|
|
9
9
|
import { FieldInfo } from "./field-info.js";
|
|
10
10
|
import { LightElement, fieldId } from "./fields.js";
|
|
11
11
|
|
|
12
|
+
// MDI's eye-outline and eye-off-outline
|
|
13
|
+
const showIcon =
|
|
14
|
+
"M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M12,4.5C17,4.5 21.27,7.61 23,12C21.27,16.39 17,19.5 12,19.5C7,19.5 2.73,16.39 1,12C2.73,7.61 7,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C15.76,17.5 19.17,15.36 20.82,12C19.17,8.64 15.76,6.5 12,6.5C8.24,6.5 4.83,8.64 3.18,12Z";
|
|
15
|
+
|
|
16
|
+
const hideIcon =
|
|
17
|
+
"M2,5.27L3.28,4L20,20.72L18.73,22L15.65,18.92C14.5,19.3 13.28,19.5 12,19.5C7,19.5 2.73,16.39 1,12C1.69,10.24 2.79,8.69 4.19,7.46L2,5.27M12,9A3,3 0 0,1 15,12C15,12.35 14.94,12.69 14.83,13L11,9.17C11.31,9.06 11.65,9 12,9M12,4.5C17,4.5 21.27,7.61 23,12C22.18,14.08 20.79,15.88 19,17.19L17.58,15.76C18.94,14.82 20.06,13.54 20.82,12C19.17,8.64 15.76,6.5 12,6.5C10.91,6.5 9.84,6.68 8.84,7L7.3,5.47C8.74,4.85 10.33,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C12.69,17.5 13.37,17.43 14,17.29L11.72,15C10.29,14.85 9.15,13.71 9,12.28L5.6,8.87C4.61,9.72 3.78,10.78 3.18,12Z";
|
|
18
|
+
|
|
12
19
|
/**
|
|
13
|
-
* A labeled native `<input>`,
|
|
14
|
-
* its form when the form's submit
|
|
15
|
-
* native one.
|
|
20
|
+
* A labeled native `<input>`, or a `<textarea>` with `multiline`, with an
|
|
21
|
+
* optional hint and error. Enter submits its form when the form's submit
|
|
22
|
+
* button is a `gw-button`, as it would with a native one.
|
|
16
23
|
*
|
|
17
24
|
* @element gw-input
|
|
18
25
|
* @summary A labeled text field in light DOM, so forms and autofill see it.
|
|
@@ -23,11 +30,20 @@ import { LightElement, fieldId } from "./fields.js";
|
|
|
23
30
|
* @attr {string} value - The value.
|
|
24
31
|
* @attr {string} placeholder - Placeholder text.
|
|
25
32
|
* @attr {string} autocomplete - The native autocomplete hint.
|
|
33
|
+
* @attr {string} inputmode - The native inputmode hint, such as `numeric` for a port, which picks the on-screen keyboard.
|
|
34
|
+
* @attr {string} min - The lowest value of a `number`, date or time field.
|
|
35
|
+
* @attr {string} max - The highest value of a `number`, date or time field.
|
|
36
|
+
* @attr {string} step - The step between values of a `number`, date or time field, or `any`.
|
|
26
37
|
* @attr {string} hint - One line of help under the field.
|
|
27
38
|
* @attr {string} info - Longer help, opened from an (i) button by the label.
|
|
28
|
-
* @attr {string} error - An error under the field, which marks it invalid.
|
|
39
|
+
* @attr {string} error - An error under the field, which marks it invalid and is announced.
|
|
29
40
|
* @attr {boolean} required - Requires a value.
|
|
41
|
+
* @attr {boolean} optional - Adds "(optional)" after the label, for the few optional fields in a form that mostly requires them.
|
|
30
42
|
* @attr {boolean} disabled - Disables the field.
|
|
43
|
+
* @attr {boolean} multiline - Uses a `<textarea>` that grows by dragging its corner, for text that runs over lines.
|
|
44
|
+
* @attr {number} rows - The textarea's starting height in lines. Defaults to 3.
|
|
45
|
+
* @attr {boolean} monospace - Sets the text in the monospace font, for paths, commands, tokens and other machine text. It also turns off spellcheck.
|
|
46
|
+
* @attr {boolean} reveal - On a password field, adds a button at the end to show and hide what's typed.
|
|
31
47
|
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The field's size, 40px tall at medium. Defaults to `medium`.
|
|
32
48
|
* @attr {string} icon - SVG path data for an icon inside the field, before the text, such as a magnifier on a search.
|
|
33
49
|
* @attr {boolean} hide-label - Keeps the label as the field's accessible name but doesn't show it, for a field in a toolbar whose icon and placeholder say what it's for.
|
|
@@ -43,11 +59,20 @@ export class GwInput extends LightElement {
|
|
|
43
59
|
value: {},
|
|
44
60
|
placeholder: {},
|
|
45
61
|
autocomplete: {},
|
|
62
|
+
inputmode: {},
|
|
63
|
+
min: {},
|
|
64
|
+
max: {},
|
|
65
|
+
step: {},
|
|
46
66
|
hint: {},
|
|
47
67
|
info: {},
|
|
48
68
|
error: {},
|
|
49
69
|
required: { type: "boolean" },
|
|
70
|
+
optional: { type: "boolean" },
|
|
50
71
|
disabled: { type: "boolean" },
|
|
72
|
+
multiline: { type: "boolean", reflect: true },
|
|
73
|
+
rows: { type: "number" },
|
|
74
|
+
monospace: { type: "boolean", reflect: true },
|
|
75
|
+
reveal: { type: "boolean" },
|
|
51
76
|
size: { reflect: true },
|
|
52
77
|
icon: {},
|
|
53
78
|
hideLabel: { type: "boolean", attribute: "hide-label", reflect: true },
|
|
@@ -59,12 +84,30 @@ export class GwInput extends LightElement {
|
|
|
59
84
|
declare value: string;
|
|
60
85
|
declare placeholder: string;
|
|
61
86
|
declare autocomplete: string;
|
|
87
|
+
/** The native inputmode hint, which picks the on-screen keyboard. */
|
|
88
|
+
declare inputmode: string;
|
|
89
|
+
/** The lowest value of a number, date or time field. */
|
|
90
|
+
declare min: string;
|
|
91
|
+
/** The highest value of a number, date or time field. */
|
|
92
|
+
declare max: string;
|
|
93
|
+
/** The step between values of a number, date or time field. */
|
|
94
|
+
declare step: string;
|
|
62
95
|
declare hint: string;
|
|
63
96
|
/** Longer help, opened from an (i) button by the label. */
|
|
64
97
|
declare info: string;
|
|
65
98
|
declare error: string;
|
|
66
99
|
declare required: boolean;
|
|
100
|
+
/** Adds "(optional)" after the label. */
|
|
101
|
+
declare optional: boolean;
|
|
67
102
|
declare disabled: boolean;
|
|
103
|
+
/** Uses a `<textarea>` instead of an `<input>`. */
|
|
104
|
+
declare multiline: boolean;
|
|
105
|
+
/** The textarea's starting height in lines. */
|
|
106
|
+
declare rows: number;
|
|
107
|
+
/** Sets the text in the monospace font and turns off spellcheck. */
|
|
108
|
+
declare monospace: boolean;
|
|
109
|
+
/** On a password field, adds a button to show and hide what's typed. */
|
|
110
|
+
declare reveal: boolean;
|
|
68
111
|
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
69
112
|
/** SVG path data for an icon inside the field, before the text. */
|
|
70
113
|
declare icon: string;
|
|
@@ -81,9 +124,41 @@ export class GwInput extends LightElement {
|
|
|
81
124
|
|
|
82
125
|
readonly #input = h("input", { id: this.#id });
|
|
83
126
|
|
|
127
|
+
readonly #textarea = h("textarea", { id: this.#id });
|
|
128
|
+
|
|
129
|
+
readonly #optional = h(
|
|
130
|
+
"span",
|
|
131
|
+
{ class: "gw-optional", hidden: true },
|
|
132
|
+
" (optional)",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
readonly #revealIcon = createIcon(showIcon);
|
|
136
|
+
|
|
137
|
+
readonly #reveal = h(
|
|
138
|
+
"button",
|
|
139
|
+
{
|
|
140
|
+
type: "button",
|
|
141
|
+
class: "gw-reveal",
|
|
142
|
+
hidden: true,
|
|
143
|
+
"aria-pressed": "false",
|
|
144
|
+
},
|
|
145
|
+
this.#revealIcon,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
#revealed = false;
|
|
149
|
+
|
|
84
150
|
readonly #hint = h("p", { class: "gw-hint", id: `${this.#id}-hint` });
|
|
85
151
|
|
|
86
|
-
readonly #error = h("p", {
|
|
152
|
+
readonly #error = h("p", {
|
|
153
|
+
class: "gw-error",
|
|
154
|
+
id: `${this.#id}-error`,
|
|
155
|
+
role: "alert",
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/** The native control in use: the textarea when multiline, else the input. */
|
|
159
|
+
get #control() {
|
|
160
|
+
return this.multiline ? this.#textarea : this.#input;
|
|
161
|
+
}
|
|
87
162
|
|
|
88
163
|
constructor() {
|
|
89
164
|
super();
|
|
@@ -93,20 +168,32 @@ export class GwInput extends LightElement {
|
|
|
93
168
|
this.value = "";
|
|
94
169
|
this.placeholder = "";
|
|
95
170
|
this.autocomplete = "";
|
|
171
|
+
this.inputmode = "";
|
|
172
|
+
this.min = "";
|
|
173
|
+
this.max = "";
|
|
174
|
+
this.step = "";
|
|
96
175
|
this.hint = "";
|
|
97
176
|
this.info = "";
|
|
98
177
|
this.error = "";
|
|
99
178
|
this.required = false;
|
|
179
|
+
this.optional = false;
|
|
100
180
|
this.disabled = false;
|
|
181
|
+
this.multiline = false;
|
|
182
|
+
this.rows = 3;
|
|
183
|
+
this.monospace = false;
|
|
184
|
+
this.reveal = false;
|
|
101
185
|
this.size = "medium";
|
|
102
186
|
this.icon = "";
|
|
103
187
|
this.hideLabel = false;
|
|
104
188
|
}
|
|
105
189
|
|
|
106
190
|
protected override create(root: HTMLElement) {
|
|
107
|
-
this.#input
|
|
108
|
-
|
|
109
|
-
|
|
191
|
+
for (const control of [this.#input, this.#textarea]) {
|
|
192
|
+
control.addEventListener("input", () => {
|
|
193
|
+
this.value = control.value;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
110
197
|
// A gw-button's native button is in its shadow root, out of the form's
|
|
111
198
|
// reach, so Enter only submits on its own with a single field. Submit
|
|
112
199
|
// here instead, as a native submit button would
|
|
@@ -131,16 +218,31 @@ export class GwInput extends LightElement {
|
|
|
131
218
|
event.preventDefault();
|
|
132
219
|
form.requestSubmit();
|
|
133
220
|
});
|
|
221
|
+
this.#reveal.addEventListener("click", () => {
|
|
222
|
+
this.#revealed = !this.#revealed;
|
|
223
|
+
this.requestUpdate("reveal");
|
|
224
|
+
});
|
|
134
225
|
root.append(
|
|
135
226
|
this.#info.heading,
|
|
136
|
-
h(
|
|
227
|
+
h(
|
|
228
|
+
"span",
|
|
229
|
+
{ class: "gw-input-control" },
|
|
230
|
+
this.#icon,
|
|
231
|
+
this.#control,
|
|
232
|
+
this.#reveal,
|
|
233
|
+
),
|
|
137
234
|
this.#hint,
|
|
138
235
|
this.#error,
|
|
139
236
|
);
|
|
140
237
|
}
|
|
141
238
|
|
|
142
239
|
protected override update() {
|
|
143
|
-
const
|
|
240
|
+
const control = this.#control;
|
|
241
|
+
const other = this.multiline ? this.#input : this.#textarea;
|
|
242
|
+
|
|
243
|
+
// Only the control in use is in the field, so the label and the form
|
|
244
|
+
// see just the one
|
|
245
|
+
if (!control.isConnected && other.isConnected) other.replaceWith(control);
|
|
144
246
|
|
|
145
247
|
const described = [
|
|
146
248
|
this.hint ? this.#hint.id : "",
|
|
@@ -149,22 +251,43 @@ export class GwInput extends LightElement {
|
|
|
149
251
|
.join(" ")
|
|
150
252
|
.trim();
|
|
151
253
|
|
|
152
|
-
this
|
|
254
|
+
const password = this.type === "password" && !this.multiline;
|
|
255
|
+
const reveal = password && this.reveal;
|
|
256
|
+
|
|
257
|
+
if (!reveal) this.#revealed = false;
|
|
258
|
+
|
|
259
|
+
this.#label.replaceChildren(this.label, this.#optional);
|
|
260
|
+
this.#optional.hidden = !this.optional;
|
|
153
261
|
this.#info.update(this.label, this.info);
|
|
154
262
|
this.#info.heading.classList.toggle("gw-visually-hidden", this.hideLabel);
|
|
155
263
|
setIconPath(this.#icon, this.icon);
|
|
156
264
|
this.#icon.toggleAttribute("hidden", !this.icon);
|
|
157
|
-
input.type = this.type;
|
|
158
|
-
|
|
159
|
-
setAttribute(
|
|
160
|
-
setAttribute(
|
|
161
|
-
setAttribute(
|
|
162
|
-
setAttribute(
|
|
163
|
-
input
|
|
164
|
-
input
|
|
265
|
+
this.#input.type = reveal && this.#revealed ? "text" : this.type;
|
|
266
|
+
this.#textarea.rows = this.rows;
|
|
267
|
+
setAttribute(control, "name", this.name || null);
|
|
268
|
+
setAttribute(control, "placeholder", this.placeholder || null);
|
|
269
|
+
setAttribute(control, "autocomplete", this.autocomplete || null);
|
|
270
|
+
setAttribute(control, "inputmode", this.inputmode || null);
|
|
271
|
+
setAttribute(this.#input, "min", this.min || null);
|
|
272
|
+
setAttribute(this.#input, "max", this.max || null);
|
|
273
|
+
setAttribute(this.#input, "step", this.step || null);
|
|
274
|
+
setAttribute(control, "spellcheck", this.monospace ? "false" : null);
|
|
275
|
+
setAttribute(control, "aria-invalid", this.error ? "true" : null);
|
|
276
|
+
setAttribute(control, "aria-describedby", described || null);
|
|
277
|
+
control.required = this.required;
|
|
278
|
+
control.disabled = this.disabled;
|
|
165
279
|
|
|
166
|
-
if (
|
|
280
|
+
if (control.value !== this.value) control.value = this.value;
|
|
167
281
|
|
|
282
|
+
this.#reveal.hidden = !reveal;
|
|
283
|
+
this.#reveal.disabled = this.disabled;
|
|
284
|
+
setAttribute(
|
|
285
|
+
this.#reveal,
|
|
286
|
+
"aria-label",
|
|
287
|
+
this.#revealed ? `Hide ${this.label}` : `Show ${this.label}`,
|
|
288
|
+
);
|
|
289
|
+
setAttribute(this.#reveal, "aria-pressed", String(this.#revealed));
|
|
290
|
+
setIconPath(this.#revealIcon, this.#revealed ? hideIcon : showIcon);
|
|
168
291
|
this.#hint.textContent = this.hint;
|
|
169
292
|
this.#hint.hidden = !this.hint;
|
|
170
293
|
this.#error.textContent = this.error;
|
package/src/gw-option-list.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface OptionItem {
|
|
|
24
24
|
readonly swatches?: readonly string[];
|
|
25
25
|
/** One color shown as a dot before the label, such as a tag's color. */
|
|
26
26
|
readonly swatch?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Shows the option but stops it being picked, such as one that isn't
|
|
29
|
+
* available here. Say why in its `description`.
|
|
30
|
+
*/
|
|
31
|
+
readonly disabled?: boolean;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
// MDI's magnify and check
|
|
@@ -202,6 +207,13 @@ export class GwOptionList extends GwElement {
|
|
|
202
207
|
color: var(--gw-color-text-strong);
|
|
203
208
|
}
|
|
204
209
|
|
|
210
|
+
/* A disabled option can still be reached, so its reason is read, but
|
|
211
|
+
reads as unavailable and can't be picked */
|
|
212
|
+
[role="option"][aria-disabled="true"] {
|
|
213
|
+
color: var(--gw-color-text-disabled);
|
|
214
|
+
cursor: not-allowed;
|
|
215
|
+
}
|
|
216
|
+
|
|
205
217
|
[role="option"] > .icon {
|
|
206
218
|
flex: none;
|
|
207
219
|
color: var(--gw-color-text-primary);
|
|
@@ -366,7 +378,7 @@ export class GwOptionList extends GwElement {
|
|
|
366
378
|
}
|
|
367
379
|
|
|
368
380
|
#pick(option: OptionItem | undefined) {
|
|
369
|
-
if (option === undefined) return;
|
|
381
|
+
if (option === undefined || option.disabled === true) return;
|
|
370
382
|
|
|
371
383
|
this.dispatchEvent(
|
|
372
384
|
new CustomEvent("pick", {
|
|
@@ -630,6 +642,7 @@ export class GwOptionList extends GwElement {
|
|
|
630
642
|
id: `${this.#id}-${index}`,
|
|
631
643
|
"data-index": index,
|
|
632
644
|
"aria-selected": selected ? "true" : "false",
|
|
645
|
+
"aria-disabled": option.disabled === true ? "true" : null,
|
|
633
646
|
"aria-setsize": this.#filtered.length,
|
|
634
647
|
"aria-posinset": index + 1,
|
|
635
648
|
"data-active": index === this.#active,
|
package/src/gw-range.ts
CHANGED
|
@@ -23,7 +23,8 @@ import { LightElement, fieldId } from "./fields.js";
|
|
|
23
23
|
* @attr {number} min - The lowest value. Defaults to 0.
|
|
24
24
|
* @attr {number} max - The highest value. Defaults to 100.
|
|
25
25
|
* @attr {number} step - The step between values. Defaults to 1.
|
|
26
|
-
* @attr {string} unit - Shown after the value in the bubble and for assistive technology, such as `%`. Empty for a plain number.
|
|
26
|
+
* @attr {string} unit - Shown after the value in the bubble, the readout and for assistive technology, such as `%`. Empty for a plain number.
|
|
27
|
+
* @attr {boolean} readout - Shows the value beside the track, kept up to date while it's dragged.
|
|
27
28
|
* @attr {string} hint - One line of help under it.
|
|
28
29
|
* @attr {boolean} disabled - Disables it.
|
|
29
30
|
*
|
|
@@ -39,6 +40,7 @@ export class GwRange extends LightElement {
|
|
|
39
40
|
max: { type: "number" },
|
|
40
41
|
step: { type: "number" },
|
|
41
42
|
unit: {},
|
|
43
|
+
readout: { type: "boolean" },
|
|
42
44
|
hint: {},
|
|
43
45
|
disabled: { type: "boolean" },
|
|
44
46
|
} as const;
|
|
@@ -53,6 +55,8 @@ export class GwRange extends LightElement {
|
|
|
53
55
|
declare step: number;
|
|
54
56
|
/** Shown after the value, such as `%`. */
|
|
55
57
|
declare unit: string;
|
|
58
|
+
/** Shows the value beside the track. */
|
|
59
|
+
declare readout: boolean;
|
|
56
60
|
declare hint: string;
|
|
57
61
|
declare disabled: boolean;
|
|
58
62
|
|
|
@@ -68,6 +72,12 @@ export class GwRange extends LightElement {
|
|
|
68
72
|
hidden: "",
|
|
69
73
|
});
|
|
70
74
|
|
|
75
|
+
readonly #readout = h("output", {
|
|
76
|
+
class: "gw-range-readout",
|
|
77
|
+
for: this.#id,
|
|
78
|
+
"aria-hidden": "true",
|
|
79
|
+
});
|
|
80
|
+
|
|
71
81
|
readonly #hint = h("p", { class: "gw-hint", id: `${this.#id}-hint` });
|
|
72
82
|
|
|
73
83
|
constructor() {
|
|
@@ -80,6 +90,7 @@ export class GwRange extends LightElement {
|
|
|
80
90
|
this.max = 100;
|
|
81
91
|
this.step = 1;
|
|
82
92
|
this.unit = "";
|
|
93
|
+
this.readout = false;
|
|
83
94
|
this.hint = "";
|
|
84
95
|
this.disabled = false;
|
|
85
96
|
}
|
|
@@ -118,10 +129,16 @@ export class GwRange extends LightElement {
|
|
|
118
129
|
this.#input.addEventListener("input", () => {
|
|
119
130
|
this.value = this.#input.valueAsNumber;
|
|
120
131
|
this.#placeBubble();
|
|
132
|
+
this.#readout.textContent = this.#text(this.value);
|
|
121
133
|
});
|
|
122
134
|
root.append(
|
|
123
135
|
this.#label,
|
|
124
|
-
h(
|
|
136
|
+
h(
|
|
137
|
+
"span",
|
|
138
|
+
{ class: "gw-range-row" },
|
|
139
|
+
h("span", { class: "gw-range-control" }, this.#input, this.#bubble),
|
|
140
|
+
this.#readout,
|
|
141
|
+
),
|
|
125
142
|
this.#hint,
|
|
126
143
|
);
|
|
127
144
|
}
|
|
@@ -150,6 +167,8 @@ export class GwRange extends LightElement {
|
|
|
150
167
|
this.unit ? this.#text(this.value) : null,
|
|
151
168
|
);
|
|
152
169
|
setAttribute(input, "aria-describedby", this.hint ? this.#hint.id : null);
|
|
170
|
+
this.#readout.textContent = this.#text(this.value);
|
|
171
|
+
this.#readout.hidden = !this.readout;
|
|
153
172
|
this.#hint.textContent = this.hint;
|
|
154
173
|
this.#hint.hidden = !this.hint;
|
|
155
174
|
}
|
package/src/gw-select.ts
CHANGED