diff --git a/.circleci/config.yml b/.circleci/config.yml index da6432cc1c0..d1aebb610fa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ executors: parameters: current_golden_images_hash: type: string - default: d95e1f5e29f6a847ce8190ad6ad6028456707b71 + default: 3ac2a24425678f3fbe475bee63696d063a4c3331 wireit_cache_name: type: string default: wireit diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index 2eee2829f45..876385dd1ed 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -5,7 +5,7 @@ Users with permissions in the `@spectrum-web-components` organization on NPM can 1. Merge all pull requests to be included in the release and wait for the `main` branch to show that it has completed the required CI jobs. 2. `git checkout main && git fetch && git pull && git clean -dfX` 3. Run `nvm use` assumes a Node Version Manager install, and confirm your on an operable version of Node. -4. `yarn install` +4. `yarn install` 5. `npm whoami` ensure that you are logged in with the user account for the public NPM registry 6. `yarn lerna-publish` 7. Scan the version summary for any unexpected changes. diff --git a/packages/overlay/stories/overlay.stories.ts b/packages/overlay/stories/overlay.stories.ts index cc20a1b3bbf..a0d1c06487c 100644 --- a/packages/overlay/stories/overlay.stories.ts +++ b/packages/overlay/stories/overlay.stories.ts @@ -162,11 +162,7 @@ const template = ({ type=${ifDefined(type)} > Show Popover - + Press Me - + Another Popover diff --git a/packages/picker/README.md b/packages/picker/README.md index abc4276fa5e..dc852ec3125 100644 --- a/packages/picker/README.md +++ b/packages/picker/README.md @@ -414,7 +414,7 @@ When the `value` of an `` matches the `value` attribute or the trimme

-Quiet: +Quiet: ` matches the `value` attribute or the trimme ``` +### Pending + +When in pending state, `` elements will not respond to click events and will be delivered with a `` to visually outline that it is pending. It will not toggle open or display its `` descendants until the attribute is removed. + +```html +Standard: + + Pass through + Normal + Multiply + Screen + +
+
+Quiet: + + Pass through + Normal + Multiply + Screen + +``` + ## Accessibility To render accessibly, an `` element should be paired with an `` element that has a `for` attribute referencing the `id` of the `` element. For an accessible label that renders within the bounds of the picker itself, but still fulfills the accessibility contract, use the `label` attribute or a `` as a child element of ``. diff --git a/packages/picker/package.json b/packages/picker/package.json index d023a7fa844..5d8011a37bf 100644 --- a/packages/picker/package.json +++ b/packages/picker/package.json @@ -74,6 +74,7 @@ "@spectrum-web-components/menu": "^0.41.2", "@spectrum-web-components/overlay": "^0.41.2", "@spectrum-web-components/popover": "^0.41.2", + "@spectrum-web-components/progress-circle": "^0.41.2", "@spectrum-web-components/reactive-controllers": "^0.41.2", "@spectrum-web-components/shared": "^0.41.2", "@spectrum-web-components/tooltip": "^0.41.2", diff --git a/packages/picker/src/Picker.ts b/packages/picker/src/Picker.ts index 0707049bb97..61a0eb69ef6 100644 --- a/packages/picker/src/Picker.ts +++ b/packages/picker/src/Picker.ts @@ -24,6 +24,7 @@ import { ifDefined, StyleInfo, styleMap, + when, } from '@spectrum-web-components/base/src/directives.js'; import { property, @@ -83,6 +84,14 @@ export class PickerBase extends SizedMixin(Focusable, { noDefaultSize: true }) { @property({ type: Boolean, reflect: true }) public invalid = false; + /** Whether the items are currently loading. */ + @property({ type: Boolean, reflect: true }) + public pending = false; + + /** Defines a string value that labels the Picker while it is in pending state. */ + @property({ type: String, attribute: 'pending-label' }) + public pendingLabel = 'Pending'; + @property() public label?: string; @@ -316,7 +325,7 @@ export class PickerBase extends SizedMixin(Focusable, { noDefaultSize: true }) { } public toggle(target?: boolean): void { - if (this.readonly) { + if (this.readonly || this.pending) { return; } this.open = typeof target !== 'undefined' ? target : !this.open; @@ -440,13 +449,28 @@ export class PickerBase extends SizedMixin(Focusable, { noDefaultSize: true }) { : html` `} - ${this.invalid + ${this.invalid && !this.pending ? html` ` : nothing} + ${when(this.pending, () => { + import( + '@spectrum-web-components/progress-circle/sp-progress-circle.js' + ); + // aria-valuetext is a workaround for aria-valuenow being applied in Firefox even in indeterminate mode. + return html` + + `; + })} management, // await the same here. @@ -843,7 +870,7 @@ export class Picker extends PickerBase { protected override handleKeydown = (event: KeyboardEvent): void => { const { code } = event; this.focused = true; - if (!code.startsWith('Arrow') || this.readonly) { + if (!code.startsWith('Arrow') || this.readonly || this.pending) { return; } if (code === 'ArrowUp' || code === 'ArrowDown') { diff --git a/packages/picker/src/picker.css b/packages/picker/src/picker.css index 25c04c92ee4..189442ed38d 100644 --- a/packages/picker/src/picker.css +++ b/packages/picker/src/picker.css @@ -89,7 +89,7 @@ sp-menu { margin-inline-start: auto; } -:host([focused]:not([quiet])) #button .picker { +:host([focused]:not([quiet], [pending])) #button .picker { /* .spectrum-Picker-trigger.focus-ring .spectrum-Picker-icon */ color: var( --spectrum-picker-icon-color-key-focus, diff --git a/packages/picker/src/spectrum-config.js b/packages/picker/src/spectrum-config.js index 95b8b72c4b8..19a48ec096b 100644 --- a/packages/picker/src/spectrum-config.js +++ b/packages/picker/src/spectrum-config.js @@ -32,6 +32,7 @@ const config = { converter.classToId('spectrum-Picker', 'button'), converter.classToAttribute('spectrum-Picker--quiet'), converter.classToAttribute('is-disabled', 'disabled'), + converter.classToAttribute('is-loading', 'pending'), converter.classToAttribute('is-invalid', 'invalid'), converter.classToAttribute('is-open', 'open'), converter.classToAttribute('is-focused', 'focused'), @@ -51,6 +52,10 @@ const config = { 'label-inline' ), converter.classToClass('spectrum-Menu-checkmark', 'checkmark'), + converter.classToClass( + 'spectrum-ProgressCircle', + 'progress-circle' + ), converter.classToClass('is-placeholder', 'placeholder'), converter.classToClass( 'spectrum-Picker-validationIcon', diff --git a/packages/picker/src/spectrum-picker.css b/packages/picker/src/spectrum-picker.css index 21a9cf48de5..67fa143db70 100644 --- a/packages/picker/src/spectrum-picker.css +++ b/packages/picker/src/spectrum-picker.css @@ -649,7 +649,7 @@ governing permissions and limitations under the License. ); } -#button.is-loading .picker { +:host([pending]) #button .picker { color: var( --highcontrast-picker-content-color-disabled, var( @@ -848,7 +848,7 @@ governing permissions and limitations under the License. } .validation-icon, -#button .spectrum-ProgressCircle { +#button .progress-circle { margin-inline-start: var( --mod-picker-spacing-text-to-alert-icon-inline-start, var(--spectrum-picker-spacing-text-to-alert-icon-inline-start) @@ -872,7 +872,7 @@ governing permissions and limitations under the License. ); } -#button .spectrum-ProgressCircle { +#button .progress-circle { margin-block-start: calc( var( --mod-picker-spacing-top-to-progress-circle, diff --git a/packages/picker/stories/args.ts b/packages/picker/stories/args.ts index 8ff6b5f76db..e6470cc33da 100644 --- a/packages/picker/stories/args.ts +++ b/packages/picker/stories/args.ts @@ -27,6 +27,7 @@ export const argTypes = { }, type: 'select', }, + options: ['s', 'm', 'l', 'xl'], }, quiet: { name: 'quiet', diff --git a/packages/picker/stories/picker-pending.stories.ts b/packages/picker/stories/picker-pending.stories.ts new file mode 100644 index 00000000000..710a8a8b2bb --- /dev/null +++ b/packages/picker/stories/picker-pending.stories.ts @@ -0,0 +1,36 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import { TemplateResult } from '@spectrum-web-components/base'; +import { argTypes } from './args'; +import { StoryArgs, Template } from './template'; + +export default { + title: 'Picker/Pending', + component: 'sp-picker', + argTypes, + args: { + pending: true, + }, +}; + +export const S = (args: StoryArgs): TemplateResult => + Template({ ...args, size: 's' }); + +export const M = (args: StoryArgs): TemplateResult => + Template({ ...args, size: 'm' }); + +export const L = (args: StoryArgs): TemplateResult => + Template({ ...args, size: 'l' }); + +export const XL = (args: StoryArgs): TemplateResult => + Template({ ...args, size: 'xl' }); diff --git a/packages/picker/stories/picker-sizes.stories.ts b/packages/picker/stories/picker-sizes.stories.ts index f7d97fff212..1f6a440b4e4 100644 --- a/packages/picker/stories/picker-sizes.stories.ts +++ b/packages/picker/stories/picker-sizes.stories.ts @@ -22,11 +22,35 @@ export default { component: 'sp-picker', argTypes: { onChange: { action: 'change' }, + invalid: { + name: 'invalid', + type: { name: 'boolean', required: false }, + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + control: { + type: 'boolean', + }, + }, + pending: { + name: 'pending', + type: { name: 'boolean', required: false }, + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + control: { + type: 'boolean', + }, + }, }, }; type StoryArgs = { onChange: (val: string) => void; + invalid: boolean; + pending: boolean; open: false; }; @@ -34,9 +58,13 @@ const picker = ({ onChange, open, size, + pending, + invalid, }: { onChange: (val: string) => void; size: 's' | 'm' | 'l' | 'xl'; + pending: boolean; + invalid: boolean; open: boolean; }): TemplateResult => { return html` @@ -51,6 +79,8 @@ const picker = ({ onChange(picker.value); }}" label="Select a Country with a very long label, too long, in fact" + ?pending="${pending}" + ?invalid="${invalid}" ?open=${open} > Deselect diff --git a/packages/picker/stories/picker.stories.ts b/packages/picker/stories/picker.stories.ts index 866bf373e72..f7806f02c4e 100644 --- a/packages/picker/stories/picker.stories.ts +++ b/packages/picker/stories/picker.stories.ts @@ -35,8 +35,10 @@ export default { invalid: false, open: false, quiet: false, + pending: false, }, argTypes: { + ...argTypes, onChange: { action: 'change' }, open: { name: 'open', @@ -48,7 +50,17 @@ export default { }, control: 'boolean', }, - ...argTypes, + pending: { + name: 'pending', + type: { name: 'boolean', required: false }, + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + control: { + type: 'boolean', + }, + }, }, }; @@ -85,6 +97,11 @@ disabled.args = { disabled: true, }; +export const invalid = (args: StoryArgs): TemplateResult => Template(args); +invalid.args = { + invalid: true, +}; + export const tooltip = (args: StoryArgs): TemplateResult => { const { open, ...rest } = args; return html` diff --git a/packages/picker/stories/template.ts b/packages/picker/stories/template.ts index 322d10403f7..3c1a064a1fd 100644 --- a/packages/picker/stories/template.ts +++ b/packages/picker/stories/template.ts @@ -23,6 +23,7 @@ export interface StoryArgs { invalid?: boolean; open?: boolean; quiet?: boolean; + pending?: boolean; showText?: boolean; onChange?: (val: string) => void; [prop: string]: unknown; diff --git a/packages/picker/test/index.ts b/packages/picker/test/index.ts index e0244f52bdf..a70d84c4b1d 100644 --- a/packages/picker/test/index.ts +++ b/packages/picker/test/index.ts @@ -45,6 +45,7 @@ import { slottedLabel, tooltip, } from '../stories/picker.stories.js'; +import { M as pending } from '../stories/picker-pending.stories.js'; import { sendMouse } from '../../../test/plugins/browser.js'; import { ignoreResizeObserverLoopError, @@ -1829,4 +1830,47 @@ export function runPickerTests(): void { expect(this.el.open).to.be.false; }); }); + describe('pending', function () { + beforeEach(async function () { + const test = await fixture(html` +
${pending({ pending: true })}
+ `); + this.label = test.querySelector('sp-field-label') as FieldLabel; + this.el = test.querySelector('sp-picker') as Picker; + await elementUpdated(this.elel); + }); + it('receives focus from an ``', async function () { + expect(this.el.focused).to.be.false; + + this.label.click(); + await elementUpdated(this.el); + + expect(this.el.focused).to.be.true; + }); + it('does not open from `click()`', async function () { + expect(this.el.open).to.be.false; + + this.el.click(); + await elementUpdated(this.el); + + expect(this.el.open).to.be.false; + }); + it('manages its "name" value in the accessibility tree when [pending]', async () => { + type NamedNode = { name: string; role: string }; + const snapshot = (await a11ySnapshot( + {} + )) as unknown as NamedNode & { + children: NamedNode[]; + }; + + expect( + findAccessibilityNode( + snapshot, + (node) => + node.name === + 'Pending Choose your neighborhood Where do you live?' + ) + ).to.not.be.null; + }); + }); }