@timmo001/groundwork-ui 0.9.7 → 0.9.9

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.
@@ -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); } /* 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-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-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; } `",
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; } `",
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
  {
@@ -1076,7 +1076,7 @@
1076
1076
  "text": "readonly CSSStyleSheet[]"
1077
1077
  },
1078
1078
  "static": true,
1079
- "default": "[ base, css` :host { display: block; } .alert { display: flex; gap: var(--gw-space-2); align-items: flex-start; padding: var(--gw-space-2) var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-primary); background: var(--gw-tone-fill, var(--gw-color-fill-info-quiet)); border: var(--gw-border-width) solid var(--gw-tone-border, var(--gw-color-border-info)); border-inline-start-width: calc(var(--gw-border-width) * 4); border-radius: var(--gw-radius-control); } ::slotted(p) { margin: 0; } ::slotted([slot=\"actions\"]) { align-self: center; } ::slotted([slot=\"icon\"]) { color: var(--gw-tone, var(--gw-color-info)); } .body { flex: 1; min-width: 0; } strong { display: block; font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } `, ]",
1079
+ "default": "[ base, css` :host { display: block; } .alert { display: flex; gap: var(--gw-space-2); align-items: flex-start; padding: var(--gw-space-2) var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-primary); background: var(--gw-tone-fill, var(--gw-color-fill-info-quiet)); border: var(--gw-border-width) solid var(--gw-tone-border, var(--gw-color-border-info)); border-inline-start-width: calc(var(--gw-border-width) * 4); border-radius: var(--gw-radius-control); } ::slotted(p) { margin: 0; } ::slotted([slot=\"actions\"]) { align-self: center; } ::slotted([slot=\"icon\"]), .icon { flex: none; color: var(--gw-tone, var(--gw-color-info)); } .body { flex: 1; min-width: 0; } strong { display: block; font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } `, ]",
1080
1080
  "inheritedFrom": {
1081
1081
  "name": "GwElement",
1082
1082
  "module": "src/element.ts"
@@ -1118,6 +1118,12 @@
1118
1118
  "privacy": "private",
1119
1119
  "readonly": true
1120
1120
  },
1121
+ {
1122
+ "kind": "field",
1123
+ "name": "#icon",
1124
+ "privacy": "private",
1125
+ "readonly": true
1126
+ },
1121
1127
  {
1122
1128
  "kind": "method",
1123
1129
  "name": "create",
@@ -9765,7 +9771,7 @@
9765
9771
  "text": "Properties"
9766
9772
  },
9767
9773
  "static": true,
9768
- "default": "{ label: {}, name: {}, type: {}, value: {}, placeholder: {}, autocomplete: {}, hint: {}, info: {}, error: {}, required: { type: \"boolean\" }, disabled: { type: \"boolean\" }, size: { reflect: true }, }",
9774
+ "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 }, }",
9769
9775
  "inheritedFrom": {
9770
9776
  "name": "GwElement",
9771
9777
  "module": "src/element.ts"
@@ -9868,12 +9874,36 @@
9868
9874
  },
9869
9875
  "default": "\"medium\""
9870
9876
  },
9877
+ {
9878
+ "kind": "field",
9879
+ "name": "icon",
9880
+ "type": {
9881
+ "text": "string"
9882
+ },
9883
+ "description": "SVG path data for an icon inside the field, before the text.",
9884
+ "default": "\"\""
9885
+ },
9886
+ {
9887
+ "kind": "field",
9888
+ "name": "hideLabel",
9889
+ "type": {
9890
+ "text": "boolean"
9891
+ },
9892
+ "description": "Keeps the label as the accessible name without showing it.",
9893
+ "default": "false"
9894
+ },
9871
9895
  {
9872
9896
  "kind": "field",
9873
9897
  "name": "#id",
9874
9898
  "privacy": "private",
9875
9899
  "readonly": true
9876
9900
  },
9901
+ {
9902
+ "kind": "field",
9903
+ "name": "#icon",
9904
+ "privacy": "private",
9905
+ "readonly": true
9906
+ },
9877
9907
  {
9878
9908
  "kind": "field",
9879
9909
  "name": "#label",
@@ -10314,6 +10344,20 @@
10314
10344
  },
10315
10345
  "description": "The field's size, 40px tall at medium. Defaults to `medium`.",
10316
10346
  "name": "size"
10347
+ },
10348
+ {
10349
+ "type": {
10350
+ "text": "string"
10351
+ },
10352
+ "description": "SVG path data for an icon inside the field, before the text, such as a magnifier on a search.",
10353
+ "name": "icon"
10354
+ },
10355
+ {
10356
+ "type": {
10357
+ "text": "boolean"
10358
+ },
10359
+ "description": "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.",
10360
+ "name": "hide-label"
10317
10361
  }
10318
10362
  ],
10319
10363
  "superclass": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "Groundwork's framework-free web components, styled by the --gw-* tokens",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/fields.ts CHANGED
@@ -108,6 +108,34 @@ export const fieldStyles = css`
108
108
  transition: border-color var(--gw-motion-fast) var(--gw-motion-easing);
109
109
  }
110
110
 
111
+ /* An icon inside the field, before the text, with the text moved clear of
112
+ it */
113
+ gw-input .gw-input-control {
114
+ position: relative;
115
+ display: flex;
116
+ align-items: center;
117
+ }
118
+
119
+ gw-input .gw-input-control > .icon {
120
+ position: absolute;
121
+ inset-inline-start: calc(var(--gw-space-3) * var(--gw-field-scale, 1));
122
+ width: calc(1.125rem * var(--gw-field-scale, 1));
123
+ height: calc(1.125rem * var(--gw-field-scale, 1));
124
+ color: var(--gw-color-text-secondary);
125
+ pointer-events: none;
126
+ fill: currentColor;
127
+ }
128
+
129
+ gw-input .gw-input-control > .icon[hidden] {
130
+ display: none;
131
+ }
132
+
133
+ gw-input .gw-input-control > .icon:not([hidden]) + input {
134
+ padding-inline-start: calc(
135
+ (var(--gw-space-3) * 2 + 1.125rem) * var(--gw-field-scale, 1)
136
+ );
137
+ }
138
+
111
139
  /* The trigger shows the chosen option, with its icon, and a chevron with
112
140
  room around it that turns over while the list is open */
113
141
  gw-select .gw-select-trigger {
@@ -276,6 +304,10 @@ export const fieldStyles = css`
276
304
  display: none;
277
305
  }
278
306
 
307
+ :where(gw-input) .gw-input-control:focus-within > .icon {
308
+ color: var(--gw-color-text-strong);
309
+ }
310
+
279
311
  :where(gw-input, gw-select) :where(input, .gw-select-trigger):hover {
280
312
  border-color: var(--gw-color-border-loud);
281
313
  }
@@ -315,7 +347,7 @@ export const fieldStyles = css`
315
347
  cursor: pointer;
316
348
  }
317
349
 
318
- :where(gw-switch, gw-checkbox, gw-select) .gw-visually-hidden {
350
+ :where(gw-input, gw-switch, gw-checkbox, gw-select) .gw-visually-hidden {
319
351
  position: absolute;
320
352
  width: 1px;
321
353
  height: 1px;
package/src/gw-alert.ts CHANGED
@@ -5,7 +5,24 @@
5
5
  * @module
6
6
  */
7
7
  import { GwElement, css, h } from "./element.js";
8
- import { base, type Tone } from "./styles.js";
8
+ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
9
+
10
+ // mdi's outlined information, check, alert and alert-decagram icons
11
+ const information =
12
+ "M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z";
13
+
14
+ const toneIcons: Readonly<Record<Tone, string>> = {
15
+ neutral: information,
16
+ accent: information,
17
+ info: information,
18
+ success:
19
+ "M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z",
20
+ warning: "M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16",
21
+ danger:
22
+ "M11,15H13V17H11V15M11,7H13V13H11V7M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20Z",
23
+ important:
24
+ "M23,12L20.56,14.78L20.9,18.46L17.29,19.28L15.4,22.46L12,21L8.6,22.47L6.71,19.29L3.1,18.47L3.44,14.78L1,12L3.44,9.21L3.1,5.53L6.71,4.72L8.6,1.54L12,3L15.4,1.54L17.29,4.72L20.9,5.54L20.56,9.22L23,12M20.33,12L18.5,9.89L18.74,7.1L16,6.5L14.58,4.07L12,5.18L9.42,4.07L8,6.5L5.26,7.09L5.5,9.88L3.67,12L5.5,14.1L5.26,16.9L8,17.5L9.42,19.93L12,18.81L14.58,19.92L16,17.5L18.74,16.89L18.5,14.1L20.33,12M11,15H13V17H11V15M11,7H13V13H11V7",
25
+ };
9
26
 
10
27
  /**
11
28
  * A message in the flow of the page. `urgent` announces it at once, for
@@ -64,7 +81,9 @@ export class GwAlert extends GwElement {
64
81
  align-self: center;
65
82
  }
66
83
 
67
- ::slotted([slot="icon"]) {
84
+ ::slotted([slot="icon"]),
85
+ .icon {
86
+ flex: none;
68
87
  color: var(--gw-tone, var(--gw-color-info));
69
88
  }
70
89
 
@@ -89,6 +108,9 @@ export class GwAlert extends GwElement {
89
108
 
90
109
  readonly #heading = h("strong");
91
110
 
111
+ // The tone's icon, shown until an icon is slotted in its place
112
+ readonly #icon = createIcon("");
113
+
92
114
  constructor() {
93
115
  super();
94
116
  this.tone = "info";
@@ -98,7 +120,7 @@ export class GwAlert extends GwElement {
98
120
 
99
121
  protected override create(root: ShadowRoot) {
100
122
  this.#alert.append(
101
- h("slot", { name: "icon" }),
123
+ h("slot", { name: "icon" }, this.#icon),
102
124
  h("div", { class: "body" }, this.#heading, h("slot")),
103
125
  h("slot", { name: "actions" }),
104
126
  );
@@ -107,6 +129,7 @@ export class GwAlert extends GwElement {
107
129
 
108
130
  protected override update() {
109
131
  this.#alert.setAttribute("role", this.urgent ? "alert" : "status");
132
+ setIconPath(this.#icon, toneIcons[this.tone] ?? information);
110
133
  this.#heading.textContent = this.heading;
111
134
  this.#heading.hidden = !this.heading;
112
135
  }
package/src/gw-input.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * @module
6
6
  */
7
7
  import { h, setAttribute } from "./element.js";
8
+ import { createIcon, setIconPath } from "./styles.js";
8
9
  import { FieldInfo } from "./field-info.js";
9
10
  import { LightElement, fieldId } from "./fields.js";
10
11
 
@@ -26,6 +27,8 @@ import { LightElement, fieldId } from "./fields.js";
26
27
  * @attr {boolean} required - Requires a value.
27
28
  * @attr {boolean} disabled - Disables the field.
28
29
  * @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The field's size, 40px tall at medium. Defaults to `medium`.
30
+ * @attr {string} icon - SVG path data for an icon inside the field, before the text, such as a magnifier on a search.
31
+ * @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.
29
32
  *
30
33
  * @cssprop --gw-field-scale - Scales the field, around 1 for medium.
31
34
  * @cssprop --gw-control-scale - Scales its height with the theme's density. The theme sets it.
@@ -44,6 +47,8 @@ export class GwInput extends LightElement {
44
47
  required: { type: "boolean" },
45
48
  disabled: { type: "boolean" },
46
49
  size: { reflect: true },
50
+ icon: {},
51
+ hideLabel: { type: "boolean", attribute: "hide-label", reflect: true },
47
52
  } as const;
48
53
 
49
54
  declare label: string;
@@ -59,9 +64,15 @@ export class GwInput extends LightElement {
59
64
  declare required: boolean;
60
65
  declare disabled: boolean;
61
66
  declare size: "xs" | "small" | "medium" | "large" | "xl";
67
+ /** SVG path data for an icon inside the field, before the text. */
68
+ declare icon: string;
69
+ /** Keeps the label as the accessible name without showing it. */
70
+ declare hideLabel: boolean;
62
71
 
63
72
  readonly #id = fieldId();
64
73
 
74
+ readonly #icon = createIcon("");
75
+
65
76
  readonly #label = h("label", { for: this.#id });
66
77
 
67
78
  readonly #info = new FieldInfo(this.#label);
@@ -86,13 +97,20 @@ export class GwInput extends LightElement {
86
97
  this.required = false;
87
98
  this.disabled = false;
88
99
  this.size = "medium";
100
+ this.icon = "";
101
+ this.hideLabel = false;
89
102
  }
90
103
 
91
104
  protected override create(root: HTMLElement) {
92
105
  this.#input.addEventListener("input", () => {
93
106
  this.value = this.#input.value;
94
107
  });
95
- root.append(this.#info.heading, this.#input, this.#hint, this.#error);
108
+ root.append(
109
+ this.#info.heading,
110
+ h("span", { class: "gw-input-control" }, this.#icon, this.#input),
111
+ this.#hint,
112
+ this.#error,
113
+ );
96
114
  }
97
115
 
98
116
  protected override update() {
@@ -107,6 +125,9 @@ export class GwInput extends LightElement {
107
125
 
108
126
  this.#label.textContent = this.label;
109
127
  this.#info.update(this.label, this.info);
128
+ this.#info.heading.classList.toggle("gw-visually-hidden", this.hideLabel);
129
+ setIconPath(this.#icon, this.icon);
130
+ this.#icon.toggleAttribute("hidden", !this.icon);
110
131
  input.type = this.type;
111
132
  setAttribute(input, "name", this.name || null);
112
133
  setAttribute(input, "placeholder", this.placeholder || null);