Skip to content

Allow :is() and :where() selectors#789

Open
KH-Coder865 wants to merge 16 commits intojupyterlab:mainfrom
KH-Coder865:allow-is-where-selectors
Open

Allow :is() and :where() selectors#789
KH-Coder865 wants to merge 16 commits intojupyterlab:mainfrom
KH-Coder865:allow-is-where-selectors

Conversation

@KH-Coder865
Copy link
Copy Markdown

This PR Changes

This PR allows the use of :is() and :where() functional pseudo-classes
in Lumino selector validation.

Historically, selectors containing commas were rejected because
comma-separated selector lists do not have a single specificity.
However, :is() and :where() are defined by Selectors Level 4
as single selectors, with well-defined specificity rules.

This change:

  • Continues to reject top-level comma-separated selectors
  • Allows commas only inside :is() and :where()
  • Does not change specificity computation logic

Fixes

Labels

  • enhancement

@KH-Coder865 KH-Coder865 force-pushed the allow-is-where-selectors branch from bf9aeeb to a92102d Compare February 6, 2026 09:07
@krassowski krassowski added the enhancement New feature or request label Feb 6, 2026
@KH-Coder865 KH-Coder865 force-pushed the allow-is-where-selectors branch from 1e4af27 to 15263f7 Compare February 7, 2026 13:55
@KH-Coder865
Copy link
Copy Markdown
Author

KH-Coder865 commented Feb 8, 2026

Have successfully moved the whole updated functionality to the domutils package, in the latest commit as advised by @krassowski .

@krassowski
Copy link
Copy Markdown
Member

The build and linter are failing. You can run tests locally or by actions on your fork and opening a PR against the fork. Please feel free to request a review once the basic build/tests/linter are passing. If you encounter issues testing it locally let me know.

@KH-Coder865
Copy link
Copy Markdown
Author

The build and linter are failing. You can run tests locally or by actions on your fork and opening a PR against the fork. Please feel free to request a review once the basic build/tests/linter are passing. If you encounter issues testing it locally let me know.

Ok Sure.

@KH-Coder865
Copy link
Copy Markdown
Author

All builds and tests pass locally except @lumino/datagrid:test, which fails due to known text rendering differences in headless Chromium.
No changes in this PR affect datagrid.

@KH-Coder865
Copy link
Copy Markdown
Author

I think it is ready for the review.

@KH-Coder865
Copy link
Copy Markdown
Author

Requesting a review @krassowski .

Copy link
Copy Markdown
Member

@krassowski krassowski left a comment

Choose a reason for hiding this comment

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

This PR should not be modifying 23 files. Can you please revert the unrelated changes to yaml and html files please?

@KH-Coder865
Copy link
Copy Markdown
Author

This PR should not be modifying 23 files. Can you please revert the unrelated changes to yaml and html files please?

I ran prettier on every file and fixed the error in the previous commit, I think it is a result of running prettier on all files.
I do not know any way to revert it.

@krassowski
Copy link
Copy Markdown
Member

git checkout main path-to-file should do it

@KH-Coder865
Copy link
Copy Markdown
Author

I’m having a bit of trouble understanding how to implement this change.
I tried looking into the command, but I’m still not sure what the correct approach is here.
Would you mind clarifying, or would it be okay if you pushed the change directly to this PR?

@KH-Coder865 KH-Coder865 force-pushed the allow-is-where-selectors branch 2 times, most recently from e990ec6 to 18d052e Compare February 13, 2026 11:29
@KH-Coder865
Copy link
Copy Markdown
Author

@krassowski
Have successfully reverted the changes. Kindly check now.

@KH-Coder865
Copy link
Copy Markdown
Author

Requesting a review.

Copy link
Copy Markdown
Member

@krassowski krassowski left a comment

Choose a reason for hiding this comment

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

Just some minor things to finish here

KH-Coder865 and others added 2 commits February 14, 2026 21:27
Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com>
@KH-Coder865
Copy link
Copy Markdown
Author

Hi @krassowski , have fixed the small changes. Hope now its good to be merged

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to allow Selectors Level 4 functional pseudo-classes :is() and :where() in Lumino selector validation by rejecting only top-level comma-separated selector lists, while permitting commas inside those pseudo-classes.

Changes:

  • Add Selector.hasTopLevelComma() to detect commas that separate selector lists (intended to ignore commas inside :is() / :where()).
  • Update selector validation in context menu items and command keybinding options to use Selector.hasTopLevelComma() instead of indexOf(',').
  • Add unit tests for hasTopLevelComma().

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
packages/domutils/src/selector.ts Introduces hasTopLevelComma() (via stripIsWhere) to allow commas inside :is()/:where().
packages/domutils/tests/src/selector.spec.ts Adds tests asserting commas inside :is()/:where() are allowed but top-level commas are rejected.
packages/widgets/src/contextmenu.ts Switches context menu selector validation to reject only top-level commas.
packages/commands/src/index.ts Switches keybinding selector validation to reject only top-level commas.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +105
/**
* Returns true if the selector contains a top-level comma.
*/
export function hasTopLevelComma(selector: string): boolean {
return stripIsWhere(selector).indexOf(',') !== -1;
}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

hasTopLevelComma() now allows selectors like .a:is(.b,.c), but Selector.calculateSpecificity()/Private.calculateSingle() still split the selector at the first comma (selector.split(',', 1)[0]). That will cut commas inside :is()/:where() and produce incorrect specificity values, which affects ordering logic in context menus and keybinding resolution. Please update specificity parsing to ignore commas inside :is()/:where() (or implement their Selectors Level 4 specificity rules) so the new allowed selectors don’t break precedence decisions.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +97
return selector.replace(
/:(is|where)\(([^()]|\([^()]*\))*\)/g,
''
);
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

stripIsWhere() uses a regex that only handles limited parenthesis nesting. Valid selectors can nest functional pseudos (e.g. :is(:not(:is(.b,.c)))), and in those cases the regex won’t strip the outer :is(...), causing commas inside nested selectors to be treated as “top-level” and rejected. Consider replacing the regex with a small scanner that tracks parentheses depth and whether the current depth is inside an :is( / :where( function.

Suggested change
return selector.replace(
/:(is|where)\(([^()]|\([^()]*\))*\)/g,
''
);
let result = '';
const len = selector.length;
let i = 0;
while (i < len) {
const ch = selector[i];
if (
ch === ':' &&
(selector.startsWith('is(', i + 1) ||
selector.startsWith('where(', i + 1))
) {
// Determine which function name we matched.
const isIs = selector.startsWith('is(', i + 1);
const nameLen = isIs ? 2 : 5; // 'is' or 'where'
const openParenIndex = i + 1 + nameLen;
// Sanity check: ensure there is an opening parenthesis.
if (openParenIndex >= len || selector[openParenIndex] !== '(') {
// Fallback: treat ':' as a normal character.
result += ch;
i++;
continue;
}
// Skip over the entire :is(...) or :where(...) construct,
// correctly handling nested parentheses.
let depth = 1;
let j = openParenIndex + 1;
while (j < len && depth > 0) {
const cj = selector[j];
if (cj === '(') {
depth++;
} else if (cj === ')') {
depth--;
}
j++;
}
// Set i to the position after the closing parenthesis (or end of string).
i = j;
continue;
}
// Normal character, keep it.
result += ch;
i++;
}
return result;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support the selectors with :is() and :where()

4 participants