Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(button): adds pending button, fixes #3162 #3163

Merged
merged 36 commits into from
Jan 5, 2024
Merged

Conversation

benjamind
Copy link
Contributor

@benjamind benjamind commented Apr 28, 2023

Description

Adds the 'pending' button state to the <sp-button> element.

The pending attribute drives the state, and internally starts the 1s cooldown timer to set the button state to disabled. The spinner will then render when the button is both disabled and pending.

Main question outstanding I think is how to manage the import here, I don't think we want all buttons automatically requiring sp-progress-circle its unnecessary page weight. My suggestion is we leave including the spinner up to the end user and maybe in dev we add a warning if its not been registered when using pending state?

Related issue(s)

Motivation and context

Pending buttons are part of the Spectrum design spec, though they should be used sparingly they are occasionally useful.

How has this been tested?

Added a unit test to confirm the button is being driven into the disabled state at the right time. I assume VRT would confirm the rest (though I have not yet updated VRT).

Screenshots (if appropriate)

Screenshot 2023-04-27 at 6 01 32 PM

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Chore (minor updates related to the tooling or maintenance of the repository, does not impact compiled assets)

Checklist

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • If my change required a change to the documentation, I have updated the documentation in this pull request.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices

Best practices

This repository uses conventional commit syntax for each commit message; note that the GitHub UI does not use this by default so be cautious when accepting suggested changes. Avoid the "Update branch" button on the pull request and opt instead for rebasing your branch against main.

@benjamind benjamind requested a review from Westbrook April 28, 2023 01:05
@github-actions
Copy link

github-actions bot commented Apr 28, 2023

Tachometer results

Chrome

action-bar permalink

Version Bytes Avg Time vs remote vs branch
npm latest 487 kB 93.81ms - 95.34ms - unsure 🔍
-2% - +2%
-1.80ms - +1.64ms
branch 481 kB 93.11ms - 96.20ms unsure 🔍
-2% - +2%
-1.64ms - +1.80ms
-

action-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 515 kB 116.27ms - 120.72ms - unsure 🔍
-1% - +3%
-0.75ms - +4.07ms
branch 510 kB 115.92ms - 117.75ms unsure 🔍
-3% - +1%
-4.07ms - +0.75ms
-

action-group permalink

Version Bytes Avg Time vs remote vs branch
npm latest 536 kB 73.07ms - 74.36ms - unsure 🔍
-4% - +1%
-3.06ms - +0.81ms
branch 531 kB 73.02ms - 76.66ms unsure 🔍
-1% - +4%
-0.81ms - +3.06ms
-

action-menu permalink

Version Bytes Avg Time vs remote vs branch
npm latest 636 kB 187.78ms - 191.88ms - unsure 🔍
-1% - +2%
-2.76ms - +2.99ms
branch 642 kB 187.70ms - 191.73ms unsure 🔍
-2% - +1%
-2.99ms - +2.76ms
-

alert-dialog permalink

Version Bytes Avg Time vs remote vs branch
npm latest 456 kB 127.70ms - 128.77ms - faster ✔
1% - 2%
0.92ms - 2.87ms
branch 451 kB 129.31ms - 130.95ms slower ❌
1% - 2%
0.92ms - 2.87ms
-

button-group permalink

Version Bytes Avg Time vs remote vs branch
npm latest 443 kB 64.37ms - 65.80ms - faster ✔
1% - 4%
0.82ms - 2.76ms
branch 438 kB 66.22ms - 67.53ms slower ❌
1% - 4%
0.82ms - 2.76ms
-

button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 449 kB 80.07ms - 83.14ms - unsure 🔍
-4% - +0%
-3.26ms - +0.34ms
branch 444 kB 82.13ms - 84.00ms unsure 🔍
-0% - +4%
-0.34ms - +3.26ms
-

dialog permalink

Version Bytes Avg Time vs remote vs branch
npm latest 508 kB 89.64ms - 91.33ms - slower ❌
1% - 3%
0.52ms - 2.34ms
branch 505 kB 88.71ms - 89.39ms faster ✔
1% - 3%
0.52ms - 2.34ms
-

infield-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 405 kB 21.54ms - 22.72ms - unsure 🔍
-2% - +4%
-0.40ms - +0.97ms
branch 397 kB 21.49ms - 22.20ms unsure 🔍
-4% - +2%
-0.97ms - +0.40ms
-

menu permalink

Version Bytes Avg Time vs remote vs branch
npm latest 464 kB 249.81ms - 255.64ms - faster ✔
8% - 11%
23.06ms - 30.80ms
branch 468 kB 277.12ms - 282.20ms slower ❌
9% - 12%
23.06ms - 30.80ms
-

meter permalink

Version Bytes Avg Time vs remote vs branch
npm latest 408 kB 64.90ms - 65.59ms - unsure 🔍
-1% - +0%
-0.64ms - +0.31ms
branch 400 kB 65.09ms - 65.73ms unsure 🔍
-0% - +1%
-0.31ms - +0.64ms
-

number-field permalink

Version Bytes Avg Time vs remote vs branch
npm latest 521 kB 107.60ms - 109.12ms - unsure 🔍
-2% - +1%
-1.76ms - +1.17ms
branch 513 kB 107.41ms - 109.91ms unsure 🔍
-1% - +2%
-1.17ms - +1.76ms
-

overlay permalink

Version Bytes Avg Time vs remote vs branch
npm latest 507 kB 58.29ms - 59.26ms - faster ✔
1% - 3%
0.48ms - 1.87ms
branch 511 kB 59.46ms - 60.45ms slower ❌
1% - 3%
0.48ms - 1.87ms
-

picker-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 417 kB 47.91ms - 48.49ms - unsure 🔍
-1% - +1%
-0.61ms - +0.30ms
branch 409 kB 48.01ms - 48.70ms unsure 🔍
-1% - +1%
-0.30ms - +0.61ms
-

picker permalink

Version Bytes Avg Time vs remote vs branch
npm latest 506 kB 641.05ms - 649.93ms - unsure 🔍
-2% - +0%
-11.50ms - +0.94ms
branch 506 kB 646.42ms - 655.13ms unsure 🔍
-0% - +2%
-0.94ms - +11.50ms
-

popover permalink

Version Bytes Avg Time vs remote vs branch
npm latest 384 kB 23.07ms - 23.35ms - unsure 🔍
-1% - +1%
-0.21ms - +0.17ms
branch 377 kB 23.10ms - 23.36ms unsure 🔍
-1% - +1%
-0.17ms - +0.21ms
-

radio permalink

Version Bytes Avg Time vs remote vs branch
npm latest 409 kB 74.68ms - 75.93ms - unsure 🔍
-1% - +1%
-0.65ms - +0.99ms
branch 401 kB 74.60ms - 75.66ms unsure 🔍
-1% - +1%
-0.99ms - +0.65ms
-

search permalink

Version Bytes Avg Time vs remote vs branch
npm latest 474 kB 63.46ms - 65.65ms - unsure 🔍
-2% - +2%
-1.15ms - +1.28ms
branch 467 kB 63.97ms - 65.01ms unsure 🔍
-2% - +2%
-1.28ms - +1.15ms
-

slider permalink

Version Bytes Avg Time vs remote vs branch
npm latest 477 kB 122.00ms - 124.46ms - unsure 🔍
-1% - +2%
-1.39ms - +1.88ms
branch 466 kB 121.91ms - 124.07ms unsure 🔍
-2% - +1%
-1.88ms - +1.39ms
-

split-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 711 kB 1854.69ms - 1857.61ms - unsure 🔍
-0% - +0%
-1.84ms - +2.19ms
branch 715 kB 1854.59ms - 1857.37ms unsure 🔍
-0% - +0%
-2.19ms - +1.84ms
-

tags permalink

Version Bytes Avg Time vs remote vs branch
npm latest 456 kB 41.63ms - 45.51ms - slower ❌
88% - 105%
19.45ms - 23.34ms
branch 449 kB 22.05ms - 22.29ms faster ✔
47% - 51%
19.45ms - 23.34ms
-

toast permalink

Version Bytes Avg Time vs remote vs branch
npm latest 436 kB 48.81ms - 49.73ms - unsure 🔍
-1% - +1%
-0.52ms - +0.70ms
branch 429 kB 48.79ms - 49.58ms unsure 🔍
-1% - +1%
-0.70ms - +0.52ms
-

tooltip permalink

Version Bytes Avg Time vs remote vs branch
npm latest 563 kB 50.52ms - 51.31ms - slower ❌
7% - 9%
3.18ms - 4.26ms
branch 547 kB 46.83ms - 47.56ms faster ✔
6% - 8%
3.18ms - 4.26ms
-
Firefox

action-bar permalink

Version Bytes Avg Time vs remote vs branch
npm latest 487 kB 175.94ms - 183.82ms - unsure 🔍
-2% - +5%
-3.04ms - +8.00ms
branch 481 kB 173.53ms - 181.27ms unsure 🔍
-4% - +2%
-8.00ms - +3.04ms
-

action-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 515 kB 274.58ms - 285.50ms - unsure 🔍
-0% - +5%
-0.35ms - +12.95ms
branch 510 kB 269.95ms - 277.53ms unsure 🔍
-5% - +0%
-12.95ms - +0.35ms
-

action-group permalink

Version Bytes Avg Time vs remote vs branch
npm latest 536 kB 184.43ms - 194.25ms - unsure 🔍
-2% - +5%
-3.31ms - +9.95ms
branch 531 kB 181.56ms - 190.48ms unsure 🔍
-5% - +2%
-9.95ms - +3.31ms
-

action-menu permalink

Version Bytes Avg Time vs remote vs branch
npm latest 636 kB 342.52ms - 352.64ms - unsure 🔍
-1% - +3%
-3.45ms - +9.97ms
branch 642 kB 339.91ms - 348.73ms unsure 🔍
-3% - +1%
-9.97ms - +3.45ms
-

alert-dialog permalink

Version Bytes Avg Time vs remote vs branch
npm latest 456 kB 226.84ms - 237.36ms - unsure 🔍
-3% - +3%
-7.03ms - +6.55ms
branch 451 kB 228.04ms - 236.64ms unsure 🔍
-3% - +3%
-6.55ms - +7.03ms
-

button-group permalink

Version Bytes Avg Time vs remote vs branch
npm latest 443 kB 174.48ms - 179.96ms - unsure 🔍
-2% - +2%
-4.33ms - +4.37ms
branch 438 kB 173.82ms - 180.58ms unsure 🔍
-2% - +2%
-4.37ms - +4.33ms
-

button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 449 kB 184.29ms - 190.63ms - unsure 🔍
-3% - +2%
-6.22ms - +2.90ms
branch 444 kB 185.84ms - 192.40ms unsure 🔍
-2% - +3%
-2.90ms - +6.22ms
-

dialog permalink

Version Bytes Avg Time vs remote vs branch
npm latest 508 kB 141.30ms - 146.98ms - unsure 🔍
-1% - +4%
-1.87ms - +5.15ms
branch 505 kB 140.44ms - 144.56ms unsure 🔍
-4% - +1%
-5.15ms - +1.87ms
-

infield-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 405 kB 58.64ms - 63.20ms - unsure 🔍
-2% - +10%
-1.04ms - +5.60ms
branch 397 kB 56.22ms - 61.06ms unsure 🔍
-9% - +2%
-5.60ms - +1.04ms
-

menu permalink

Version Bytes Avg Time vs remote vs branch
npm latest 464 kB 437.54ms - 446.22ms - unsure 🔍
-2% - +1%
-10.20ms - +4.16ms
branch 468 kB 439.18ms - 450.62ms unsure 🔍
-1% - +2%
-4.16ms - +10.20ms
-

meter permalink

Version Bytes Avg Time vs remote vs branch
npm latest 408 kB 116.78ms - 123.78ms - unsure 🔍
-2% - +6%
-2.53ms - +6.53ms
branch 400 kB 115.41ms - 121.15ms unsure 🔍
-5% - +2%
-6.53ms - +2.53ms
-

number-field permalink

Version Bytes Avg Time vs remote vs branch
npm latest 521 kB 230.17ms - 241.31ms - unsure 🔍
-2% - +4%
-3.50ms - +9.06ms
branch 513 kB 230.06ms - 235.86ms unsure 🔍
-4% - +1%
-9.06ms - +3.50ms
-

overlay permalink

Version Bytes Avg Time vs remote vs branch
npm latest 582 kB 133.65ms - 137.75ms - unsure 🔍
-1% - +4%
-1.61ms - +4.93ms
branch 578 kB 131.49ms - 136.59ms unsure 🔍
-4% - +1%
-4.93ms - +1.61ms
-

picker-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 417 kB 85.72ms - 93.16ms - unsure 🔍
-5% - +6%
-4.40ms - +5.40ms
branch 409 kB 85.76ms - 92.12ms unsure 🔍
-6% - +5%
-5.40ms - +4.40ms
-

picker permalink

Version Bytes Avg Time vs remote vs branch
npm latest 506 kB 1002.60ms - 1024.00ms - unsure 🔍
-1% - +2%
-10.92ms - +17.24ms
branch 506 kB 1000.99ms - 1019.29ms unsure 🔍
-2% - +1%
-17.24ms - +10.92ms
-

popover permalink

Version Bytes Avg Time vs remote vs branch
npm latest 384 kB 54.41ms - 57.43ms - unsure 🔍
-0% - +9%
-0.08ms - +4.56ms
branch 377 kB 51.92ms - 55.44ms unsure 🔍
-8% - +0%
-4.56ms - +0.08ms
-

radio permalink

Version Bytes Avg Time vs remote vs branch
npm latest 409 kB 152.39ms - 162.25ms - unsure 🔍
-2% - +6%
-2.91ms - +9.83ms
branch 401 kB 149.82ms - 157.90ms unsure 🔍
-6% - +2%
-9.83ms - +2.91ms
-

search permalink

Version Bytes Avg Time vs remote vs branch
npm latest 474 kB 117.76ms - 125.76ms - unsure 🔍
-4% - +5%
-4.96ms - +6.04ms
branch 467 kB 117.45ms - 124.99ms unsure 🔍
-5% - +4%
-6.04ms - +4.96ms
-

slider permalink

Version Bytes Avg Time vs remote vs branch
npm latest 477 kB 220.04ms - 230.96ms - unsure 🔍
-3% - +4%
-5.63ms - +7.99ms
branch 466 kB 220.25ms - 228.39ms unsure 🔍
-4% - +2%
-7.99ms - +5.63ms
-

split-button permalink

Version Bytes Avg Time vs remote vs branch
npm latest 711 kB 1567.44ms - 1571.36ms - unsure 🔍
-0% - -0%
-7.55ms - -2.25ms
branch 715 kB 1572.52ms - 1576.08ms unsure 🔍
+0% - +0%
+2.25ms - +7.55ms
-

tags permalink

Version Bytes Avg Time vs remote vs branch
npm latest 456 kB 46.61ms - 51.67ms - unsure 🔍
-4% - +10%
-1.77ms - +4.73ms
branch 449 kB 45.61ms - 49.71ms unsure 🔍
-10% - +3%
-4.73ms - +1.77ms
-

toast permalink

Version Bytes Avg Time vs remote vs branch
npm latest 436 kB 108.26ms - 114.26ms - unsure 🔍
-2% - +5%
-2.24ms - +5.48ms
branch 429 kB 107.21ms - 112.07ms unsure 🔍
-5% - +2%
-5.48ms - +2.24ms
-

tooltip permalink

Version Bytes Avg Time vs remote vs branch
npm latest 644 kB 104.84ms - 109.32ms - slower ❌
12% - 17%
11.29ms - 15.87ms
branch 640 kB 93.03ms - 93.97ms faster ✔
11% - 15%
11.29ms - 15.87ms
-

Copy link
Contributor

@Westbrook Westbrook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those helpers in the buttons do help a good deal of boilerplate, but there is a lot left there 😓

Good first pass, a couple of areas to confirm what we want to offer in the API and then away we go!

Comment on lines 80 to 89
if (value) {
this.pendingCooldown = window.setTimeout(() => {
if (!this.disabled) {
this.disabledByPending = true;
}
this.disabled = true;
}, 1000);
} else {
window.clearTimeout(this.pendingCooldown);
if (this.disabledByPending) {
this.disabled = false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling the button does slightly different things to the accessibility tree delivered than I think that we want to here. Name you get "Button, dimmed". We likely want to surface a secondary label as an attribute that we can apply when when pending. We're looking at this in #3110 for Picker, so we should make sure the approaches are aligned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch to using is-pending state, and modified the spectrum-css conversion config to add the right is-pending attribute where it finds disabled. This is working with a bit of a hack, and I'm not sure of the 'right' fix here, though I think the hack has a low chance of failing?

See here https://github.com/adobe/spectrum-web-components/pull/3163/files#diff-68a09e148bb1fa4125cc905579fa756d38725e86244b153a1ec4c819eaffed5fR44

Comment on lines 184 to 188
<sp-progress-circle
indeterminate
static="white"
size="s"
></sp-progress-circle>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this always be available in the doc? If that's the approach we want, then we'll need something like aria-hidden=${ifDefined(!this.pending ? 'true' : undefined)} to keep it off of the accessibility tree.

Also, it doesn't look like this registration is ever loaded. Should it be static, dynamic, in the docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah my feeling is it should be in the docs only (you don't want to lazy load this it might not show up for long as you're only supposed to use these pending states for < 5s loads). Maybe a dev mode warning if its used and pending-circle isn't registered?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified this to only add the element when needed, this avoids the aria issue. Have still not made a decision on importing, I'm leaning towards just documentation of the need for sp-progress-circle being a required dependency when using pending state though? This would then avoid unnecessary payload. Maybe add a dev warning if the element is not registered?


:host([pending][disabled]) slot[name='icon'],
:host([pending][disabled]) #label {
visibility: hidden;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't remember what visibly hiding this content does to the accessibility tree right off, so making a note to confirm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked in voiceover and the label seems to be hidden? Is there some other way I can test?

<sp-progress-circle
indeterminate
static="white"
size="s"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We likely want to match the t-shirt size approach to that of #3110 as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah i had a play with this, was 'tricky', one step down in size was not always the rule, and the sizes didn't seem to align well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some more testing here and there's no good sizes here that work. s size progress-circle on s sized button is too big, s on m button is about right, but m on l button is too big.

Not sure what to do here, we could apply scale on the transform for the progress-circle to tweak it at each t-shirt size perhaps? But it'll likely be a custom value per button size.

await elementUpdated(el);

expect(el.disabled).to.be.true;
expect(el.pending).to.be.true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using an accessibility snap shot here could support us in guaranteeing a specific entry in the a11y tree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use the a11y snapshot testing for the aria-label changing to the pending-label value.

@Westbrook
Copy link
Contributor

@benjamind are you able to look into getting this over the line?

@Westbrook Westbrook marked this pull request as draft August 18, 2023 12:30
@benjamind
Copy link
Contributor Author

@benjamind are you able to look into getting this over the line?

Only a month later.....(my SWC notifications are a mess)...yes I can try and find some time now to push this over.

@benjamind benjamind force-pushed the delarre/pending-button branch from a29b77d to 5009c7a Compare September 13, 2023 00:34
@TarunAdobe TarunAdobe marked this pull request as ready for review December 4, 2023 07:55
@TarunAdobe TarunAdobe requested a review from Westbrook December 4, 2023 16:21
packages/radio/src/radio.css Show resolved Hide resolved
Comment on lines 61 to 74
this.addEventListener(
'click',
(event: Event): void | boolean => {
if (this.pending) {
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
return false;
}
},
{
capture: true,
}
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on this versus using pointer-events: none on the attribute selector?

If we use the event listener, I think we should only add it when pending rather than always have the listener attached.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pointer-events:none should be better in this case so as to avoid any event propagation at all during pending state.
Note: we are overriding the click function to avoid clicking on pending either way so even with pointer-events:none we won't run into cases where users tries to click the button programatically while in pending state!

Comment on lines 77 to 84
@property({ type: Boolean, reflect: true, attribute: 'is-pending' })
public isPending = false;

@property({ type: String, attribute: 'pending-label' })
public pendingLabel = 'Pending';

@property({ type: Boolean, reflect: true, attribute: true })
public pending = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand the use of pending and isPending. Can you add some comments to clarify the workflow that's being acheived?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah pending is the indication that this is a pending state button, while is-pending is added/removed during the pending state. Agree needs comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That timing seems weird, though it does make the code make so much more sense.

Based on this documentation, it feels like pending should be an immediate state. Was there something specific to your application requirements that lead to this relationship?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it was more a matter of convenience at point of use. If the pending state is added as an indicator of future behavior, then the click handler behaves like a normal button, with the click event firing on first click. The button then adds its own isPending state to track its timeout period and disable further clicks during that time.

If the consumer has to add the pending state when click occurs, thats a little more work for the consumer to deal with? Not sure, I could go both ways on this, but it felt convenient to have it this way, and it marks the button in the consumers render template as a pending state button so its quite clear to see that the behavior will be distinct here, rather than a side effect of the buttons click handler?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting approach, but not seemingly what the code says?

        if (changedProperties.has('pending')) {
            if (this.pending) {
                this.pendingCooldown = window.setTimeout(() => {
                    if (!this.disabled) {
                        this.isPending = true;
                        this.setAttribute('aria-label', this.pendingLabel);
                    }
                }, 1000);
            } else if (this.pending === false && this.isPending) {
                window.clearTimeout(this.pendingCooldown);
                this.isPending = false;
                this.setAttribute('aria-label', this.label || '');
            }
        }

This may be updated functionality, but this says that as soon as you set pending you start the clock for isPending.

I discussed why this might be in with the design team, and they outlined a seemingly valid use case of no wanting to go to "visually pending" and then "processed" in flicker speed progression. It's valid, but it's valid at any point in the process where you might switch to pending, which makes me wonder if it. would be better to require an application manage any staged loading they do local to where there can be some understanding of the time required to do the actual task in question.

I can be convinced of either way, with clear comments that outline a workflow that we all see the same way, but right now I'm leaning towards it being better to only offer pending and it being immediate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes sorry forgot that nuance (its been a while since i started this one)....

I think if we leave management of the visually pending state to the consumer, like as not it will get implemented poorly in many places and result in a multitude of flickering bugs in a variety of places with solutions to it varying across the board.

But yes the state flow as I recall now is:

  • Enabled button
  • Click
  • Click handler - consumer assigns pending state
  • isPending timeout begins
  • isPending && pending causes display of 'visually pending' state
  • Time goes by
  • Consumer removes pending state
  • isPending is removed
  • Enabled button.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is ugly pretty much regardless... @TarunAdobe can you test this approach which would simplify to a single state variable.

  • Enable button
  • Click
  • Consumer assignes pending
  • The 'visually pending' state is applied via an immediate transition or animation that has a delay of 1000ms but is customizable via a CSS Custom Property
  • Time goes by
  • Consumer removes pending
  • The 'visually pending' state is removed via an immediate transition or animation that has no delay
  • Button is enable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

<sp-progress-circle
indeterminate
static="white"
size="s"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is size="s" the right size for all Button sizes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In internal discussion with the Spectrum-CSS team, specifically @KayiuCarlson we concluded that there are no good t-shirt sizes right now to use for the progress circle inside of a button in these cases. So we concluded that we should fudge the sizes manually here to match the workflow-icon sizes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now using tshirt sizing for progress-circle.

Comment on lines 34 to 36
sp-progress-circle {
display: none;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any time when not pending that this would be rendered to the DOM so as to need this rule?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checked and found this is redundant!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the removal of isPending variable in the button. this is now back and useful again as we want to show the progress-circle only when the is-pending attribute is set after the animation

Comment on lines 25 to 27
::slotted([slot='icon']) {
inset: unset;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this one... It doesn't seem to do anything in context of what we are trying to achieve.

Comment on lines 29 to 32
#container {
position: relative;
display: flex;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This element no longer seems to exist?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so... maybe it did during the initial phase of this PR however now it's useless. so im removing it.

Comment on lines +45 to +46
left: 50%;
transform: translate(-50%, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference: left here is technically correct, in that translate() doesn't really support logical properties, but CSS attempts not to use it when possible.

@TarunAdobe TarunAdobe requested a review from Westbrook December 7, 2023 14:30
@TarunAdobe
Copy link
Contributor

@Westbrook I have added the tshirt sizing for the progress-circle... Does this PR need more work?

<sp-progress-circle
indeterminate
static="white"
label=${this.pendingLabel + 'progress circle'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the progress circle part? That forces un-translated content into the label.

Separately, is it even possible to get to the Progress Circle in the accessibility tree? Would it be better to use aria-hidden="true", as this is namely a visible affordance for the change in label on the Button?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right! this is just a visible affordance so it makes much more sense to have aria-hidden="true"

@TarunAdobe TarunAdobe requested a review from Westbrook December 12, 2023 07:27
@TarunAdobe TarunAdobe requested a review from Westbrook December 19, 2023 11:38
Comment on lines +25 to +43
@keyframes show-progress-circle {
0% {
visibility: hidden;
}

100% {
visibility: visible;
}
}

@keyframes hide-icons-label {
0% {
visibility: visible;
}

100% {
visibility: hidden;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really wanted that to work nicely 😞 Seems like it's possible, but the actual complexities of it out weight the benefits. Thanks for checking!

Comment on lines 161 to 162
changedProperties.get('pending') !== undefined &&
!this.pending
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you see us addressing by the more complex condition here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a consumer only provides the aria-label property for a button and doesn't set the label then this condition would make sure we won't override the aria-label value to this.label which is undefined in this case.
Note: sp-split-button element currently does the same thing... we provide the aria-label to the buttons but not the label.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Help me understand. How I currently understand the code as written:

if ("pending has changed") {
    if ("is pending") {
        if ("is not disabled") {
            // Apply pending label without regard for whether it is present or not
        }
    } else if ("is NOT pending AND NOT previously `undefined`, which happens at construction time when creating the `pending` property to begin with") {
        // Apply `label` or ''
    }
}

I like the idea of covering for when the author has applied aria-label, but I'm not sure that this code does. I think doing so would look more like:

if ("pending has changed") {
    if ("is pending") {
        if ("is not disabled") {
            // Assume the applied `aria-label`, no matter who set it, is correct (this _might_ hit some issues if a Button was created as `pending` to start with, depending on code order)
            this.cachedLabel = this.getAttribute('aria-label');
            this.setAttribute('aria-label', this.pendingLabel);
        } else if (this.cachedLabel) {
            // If there was previously an applied `aria-label` re-apply it, or do nothing
            this.setAttribute('aria-label', this.cachedLabel);
        }
    }
}

This logic then duplicates some complexity with the change logic for disabled, which might benefit from being merged in, but is more annoying.

Either way, this got complex fast, it will be important to confirm the value of the arial-label attribute across these various states in the unit tests.

  • pending===false => pending===true
  • disabled===true && pending===false => disabled===true && pending===false
  • disabled===false && pending===true => disabled===true && pending===true
  • disabled===true && pending===true => disabled===false && pending===true
  • All the same again with aria-label applied from the outside
  • All the same again with label applied from the outside

There may be others, so think through the logic your creating and make sure the various workflows are fully managed so that the output you're expecting at the various points is specifically under test in the tests you add.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the logic and added some new tests. I think they cover all of the possibilities (?) However the logic is now a bit complicated so if you need help in understanding anything or if I missed some edge-cases do let me know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Westbrook I've updated the code based on our discussion. Have a look whenever you get time

Comment on lines 168 to 170
if (this.pending && !this.disabled) {
this.setAttribute('aria-label', this.pendingLabel);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (this.pending && this.disabled) should we remove the aria-label in this case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right yes!

@TarunAdobe TarunAdobe requested a review from Westbrook December 21, 2023 05:20
@TarunAdobe TarunAdobe self-assigned this Jan 3, 2024
@Westbrook Westbrook changed the title feat(button): adds pending button, fixes #3162 fix(button): adds pending button, fixes #3162 Jan 3, 2024
Copy link
Contributor

@Westbrook Westbrook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last ones a nit but if we can get that back in, let's ship it!

@@ -127,7 +146,6 @@ export class Button extends SizedMixin(ButtonBase, { noDefaultSize: true }) {
public set quiet(quiet: boolean) {
this.treatment = quiet ? 'outline' : 'fill';
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add back this line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done hehe

@TarunAdobe TarunAdobe requested a review from Westbrook January 5, 2024 13:30
Copy link
Contributor

@Westbrook Westbrook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thanks for driving this home. :shipit:

@Westbrook Westbrook merged commit 71254ec into main Jan 5, 2024
47 checks passed
@Westbrook Westbrook deleted the delarre/pending-button branch January 5, 2024 13:55
@benjamind
Copy link
Contributor Author

Amazing work @TarunAdobe thanks for making this a real thing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feat]: Support 'pending' button states
3 participants