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(combobox, stepper, table): respect user hidden attribute #10983

Merged
merged 31 commits into from
Jan 22, 2025

Conversation

josercarcamo
Copy link
Contributor

@josercarcamo josercarcamo commented Dec 4, 2024

Related Issue: #8623

Summary

Made items with the hide-item attribute added by the user stay hidden.

This PR supersedes #10515

Wiki page

https://github.com/Esri/calcite-design-system/wiki/hiddenItem-Property

@github-actions github-actions bot added the bug Bug reports for broken functionality. Issues should include a reproduction of the bug. label Dec 4, 2024
@jcfranco
Copy link
Member

@josercarcamo This is missing the utilities mentioned in item 2 from the previous review. Could you update your PR to include them?

Since this supersedes the previous PR, can you close it?

Copy link
Member

@jcfranco jcfranco left a comment

Choose a reason for hiding this comment

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

Good progress, @josercarcamo! 😎 This needs a few more tweaks, and it'll be good to go!

@@ -97,10 +97,8 @@
}

@mixin base-component() {
:host([hidden]) {
Copy link
Member

Choose a reason for hiding this comment

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

Since the internal hide-item attribute is only applicable to certain components, can you create a separate mixin and include in applicable children?

* Specifies whether the user set the hidden attribute in the HTML
*
*/
@property() hideItem = false;
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for removing user from the prop name! To align with hidden, WDYT about hiddenItem?

@@ -262,37 +262,48 @@ describe("calcite-stepper", () => {
<calcite-stepper-item heading="Step 3" id="step-3">
<div>Step 3 content</div>
</calcite-stepper-item>
<calcite-stepper-item heading="Step 4" id="step-4" hide-item>
Copy link
Member

Choose a reason for hiding this comment

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

FIXME: hide-item is internal, so it shouldn't be used in tests. You'll need to update lines like the following to use the internal prop instead. This change will ensure users can hide elements without our components making them unintentionally visible.

@@ -1642,9 +1689,8 @@ describe("keyboard navigation", () => {
await page.keyboard.press("PageUp");
await page.waitForChanges();
expect(await getFocusedElementProp(page, "id")).toBe("head-1b");
await page.keyboard.down("ControlRight");
page.keyboard.press("ControlRight");
Copy link
Member

Choose a reason for hiding this comment

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

This should use await and changing it to press will cause it to no longer go into the expected End + ControlRight path here (i.e., ControlRight is pressed and released before End is pressed).

Copy link
Contributor

This PR has been automatically marked as stale because it has not had recent activity. Please close your PR if it is no longer relevant. Thank you for your contributions.

@github-actions github-actions bot added the Stale Issues or pull requests that have not had recent activity. label Dec 20, 2024
@github-actions github-actions bot removed the Stale Issues or pull requests that have not had recent activity. label Jan 9, 2025
@josercarcamo
Copy link
Contributor Author

@jcfranco please review again.

Copy link
Member

@jcfranco jcfranco left a comment

Choose a reason for hiding this comment

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

Great stuff, @josercarcamo! One more pass and this should be good to go!

BTW, these changes caused some combobox and input-time-zone tests to fail, so they'll need to be updated.

@@ -863,6 +863,56 @@ describe("calcite-combobox", () => {
}
});

it("should show correct number of items when child hidden", async () => {
const page = await newE2EPage();
await page.setContent(`
Copy link
Member

Choose a reason for hiding this comment

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

Can you use the html formatting helper, so prettier formats embedded HTML? Applies to other test page markup.

</calcite-stepper>`,
);
it("should select the next enabled stepper-item if first stepper-item is disabled or hidden", async () => {
for (const n of ["disabled", "hidden"]) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's use it.each for this instead.

@@ -843,6 +877,9 @@ describe("calcite-stepper", () => {
<calcite-stepper-item heading="Step 2" id="step-2" disabled>
<div>Step 2 content</div>
</calcite-stepper-item>
<calcite-stepper-item heading="Step 2.1" id="step-2.1" hidden>
Copy link
Member

Choose a reason for hiding this comment

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

For consistency, can you update all steps to be integers?

@@ -274,6 +281,8 @@ export class StepperItem extends LitElement implements InteractiveComponent, Loa
private handleItemClick(event: MouseEvent): void {
if (
this.disabled ||
this.hiddenItem ||
Copy link
Member

Choose a reason for hiding this comment

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

This can use isHidden, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I run the e2e tests, this is what I get:

image

Copy link
Member

Choose a reason for hiding this comment

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

Can you try passing the element instead of the instance? You'll get that error if you pass the latter.

@@ -26,3 +29,7 @@ export function warnIfMissingRequiredProp<C extends LitElement>(
logger.warn(`[${component.el.localName}] "${newProp.toString()}" or "${deprecatedProp.toString()}" is required.`);
}
}

export function isHidden<C extends ComboboxChildElement | StepperItem["el"] | TableRow["el"]>(component: C): boolean {
return component?.hidden || component?.hiddenItem;
Copy link
Member

Choose a reason for hiding this comment

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

Can component be undefined here?

const items = await page.findAll("calcite-table-row");
for (let i = 0; i < items.length; i++) {
const style = await items[i].getComputedStyle();
expect(style["display"]).toBe(i === 2 || i === 3 ? "none" : "contents");
Copy link
Member

Choose a reason for hiding this comment

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

This test might go away, but I think you can use E2EElement.isVisible for these assertions.

@@ -34,6 +34,41 @@ describe("calcite-table", () => {
</calcite-table>`,
{ display: "flex" },
);

it("renders only non-hidden rows", async () => {
Copy link
Member

Choose a reason for hiding this comment

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

I think we can drop this test since it's testing native browser behavior.

@@ -26,3 +29,7 @@ export function warnIfMissingRequiredProp<C extends LitElement>(
logger.warn(`[${component.el.localName}] "${newProp.toString()}" or "${deprecatedProp.toString()}" is required.`);
}
}

export function isHidden<C extends ComboboxChildElement | StepperItem["el"] | TableRow["el"]>(component: C): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

For consistency, can we rename the input argument to el?

@josercarcamo
Copy link
Contributor Author

@jcfranco one more review please.

Copy link
Member

@jcfranco jcfranco left a comment

Choose a reason for hiding this comment

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

👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻
👻✅👻👻👻✅👻✅✅✅👻👻✅✅✅👻✅✅✅✅👻✅👻
👻✅✅👻👻✅👻👻✅👻👻✅👻👻👻👻✅👻👻👻👻✅👻
👻✅👻✅👻✅👻👻✅👻👻✅👻👻👻👻✅✅✅👻👻✅👻
👻✅👻👻✅✅👻👻✅👻👻✅👻👻👻👻✅👻👻👻👻👻👻
👻✅👻👻👻✅👻✅✅✅👻👻✅✅✅👻✅✅✅✅👻✅👻
👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻👻

Once comments are addressed, this will be good to merge! Thanks, @josercarcamo!

@@ -53,6 +53,13 @@ export class ComboboxItemGroup extends LitElement {
*/
@property() scale: Scale = "m";

/**
* Specifies whether the user set the hidden attribute in the HTML
Copy link
Member

Choose a reason for hiding this comment

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

This comment doesn't seem to be accurate. Can you give it another look? Applies to most hiddenItem prop doc.

);
it.each(["disabled", "hidden"])(
"should select the next enabled stepper-item if first stepper-item is %s",
async (accessibility) => {
Copy link
Member

Choose a reason for hiding this comment

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

IMO, accessibility seems out of place here. Would stateProp be a better fit?

@@ -390,7 +391,8 @@ export class Stepper extends LitElement {

private handleDefaultSlotChange(event: Event): void {
const items = slotChangeGetAssignedElements(event).filter(
(el): el is StepperItem["el"] => el?.tagName === "CALCITE-STEPPER-ITEM",
(el): el is StepperItem["el"] =>
el?.tagName === "CALCITE-STEPPER-ITEM" && !isHidden(el as StepperItem["el"]),
Copy link
Member

Choose a reason for hiding this comment

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

Can you confirm if you need to typecast here? It looks like el is properly typed by the filter's return type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like the type casting is needed:

image

Copy link
Member

@driskull driskull left a comment

Choose a reason for hiding this comment

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

Looking good @josercarcamo 👍

@@ -26,3 +29,7 @@ export function warnIfMissingRequiredProp<C extends LitElement>(
logger.warn(`[${component.el.localName}] "${newProp.toString()}" or "${deprecatedProp.toString()}" is required.`);
}
}

export function isHidden<C extends ComboboxChildElement | StepperItem["el"] | TableRow["el"]>(el: C): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could have an interface for HiddenItem that each of these components could implement? Then this utility may not be necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think an interface is appropriate here since they all behave the same. A parent class would be more appropriate. This way we wouldn't need to pass anything to the method. However, we would only have this one method.

Copy link
Member

Choose a reason for hiding this comment

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

Now that we're using lit, we may be able to have a class that these could extend.

Copy link
Member

Choose a reason for hiding this comment

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

We're moving more towards controllers, so we could enforce the interface this way. This would be handled separately from this PR.

Copy link
Member

Choose a reason for hiding this comment

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

We can handle this separately with a followup

*
* @private
*/
@property({ reflect: true }) hiddenItem = false;
Copy link
Member

Choose a reason for hiding this comment

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

Should we have e2e defaults and reflects tests for each of these new private properties?

Copy link
Member

Choose a reason for hiding this comment

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

Not for internal props, but the controller approach could help us test this behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Not for internal props, but the controller approach could help us test this behavior.

It would be beneficial even for internal props because if this property isn't reflected then the CSS and querySelector logic won't work. I would think it would be beneficial

Copy link
Member

Choose a reason for hiding this comment

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

That should be handled by the controller testing.

…github.com:Esri/calcite-design-system into josercarcamo/8623-respect-user-hidden-attribute-v2
@josercarcamo
Copy link
Contributor Author

@driskull are you ok with handling your two comments in a separate PR and merge this PR for now?

*
* @private
* */
@property({ reflect: true }) hiddenItem = false;
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: would itemHidden be preferred here? it aligns better with our other props like feature-disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had proposed that but it was decided to go with hiddenItem.

Copy link
Member

Choose a reason for hiding this comment

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

@jcfranco what you think? 🤔 It doesn't matter much since its a private property but would align better with other props as itemHidden or even calciteHidden to illustrate that calcite is hiding this.

Copy link
Member

Choose a reason for hiding this comment

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

Renaming sounds good for consistency. @josercarcamo I'll defer to you whether you want to tackle that here or separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll tackle it here. I'll change it to calciteHidden, if there are no objections.

@driskull
Copy link
Member

Should we have a followup issue to use itemHidden/hiddenItem in place of filterHidden and any other props we use to hide items?

@josercarcamo
Copy link
Contributor Author

@driskull I'll create a separate issue as renaming in other components might need further changes and testing. I just want to close this one knowing that it is solid. Then we can make further changes.

@jcfranco jcfranco requested a review from driskull January 22, 2025 19:18
@driskull
Copy link
Member

I'll create a separate issue as renaming in other components might need further changes and testing. I just want to close this one knowing that it is solid. Then we can make further changes.

Sounds good. I like calcite-hidden as it aligns with our other global attributes like calcite-hydrated.

Copy link
Member

@driskull driskull left a comment

Choose a reason for hiding this comment

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

👍

@jcfranco
Copy link
Member

jcfranco commented Jan 22, 2025

Sounds good. I like calcite-hidden as it aligns with our other global attributes like calcite-hydrated.

Sorry for chiming in late here, but calcite-hydrated is a special case, doesn't have a corresponding prop, and we don't prefix internal props with calcite. I'd suggest itemHidden, sticking with the previous name or to find an alternative name.

This can be done as a follow-up since it is internal.

@josercarcamo josercarcamo added the pr ready for visual snapshots Adding this label will run visual snapshot testing. label Jan 22, 2025
@josercarcamo josercarcamo merged commit 42fd9c5 into dev Jan 22, 2025
14 checks passed
@josercarcamo josercarcamo deleted the josercarcamo/8623-respect-user-hidden-attribute-v2 branch January 22, 2025 22:05
benelan added a commit that referenced this pull request Jan 24, 2025
* origin/dev: (34 commits)
  build: update browserslist db (#11339)
  build(deps): update dependency lint-staged to v15.4.1 (#11343)
  chore: release next
  feat(graph): add component tokens (#11355)
  chore: release next
  fix(popover, tooltip): drop relative-positioning to reduce risk of clipping (#11373)
  chore: release next
  fix(date-picker): no longer disable min/max value month in select menu (#11350)
  chore(icon): improve icon load error message (#11367)
  fix(date-picker): remove outline for header actions (#11369)
  chore: release next
  fix(carousel): Ensure correct `autoplay` display and animation (#11338)
  chore: release next
  fix(flow): process items on loaded (#11364)
  chore: release next
  fix(combobox, stepper, table): respect user hidden attribute (#10983)
  refactor(action-pad): restore rounded styling (#11358)
  test(combobox): avoid emitting change event for value property update (#11281)
  chore: release next
  refactor(action-pad): remove unnecessary overflow css styling. (#11349)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Bug reports for broken functionality. Issues should include a reproduction of the bug. pr ready for visual snapshots Adding this label will run visual snapshot testing.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants