mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Use valid CSS selectors in useId format (#32001)
For the `useId` algorithm we used colon `:` before and after. https://github.com/facebook/react/pull/23360 This avoids collisions in general by using an unusual characters. It also avoids collisions when concatenated with some other ID. Unfortunately, `:` is not a valid character in `view-transition-name`. This PR swaps the format from: ``` :r123: ``` To the unicode: ``` «r123» ``` Which is valid CSS selectors. This also allows them being used for `querySelector()` which we didn't really find a legit use for but seems ok-ish. That way you can get a view-transition-name that you can manually reference. E.g. to generate styles: ```js const id = useId(); return <> <style>{` ::view-transition-group(${id}) { ... } ::view-transition-old(${id}) { ... } ::view-transition-new(${id}) { ... } `}</style> <ViewTransition name={id}>...</ViewTransition> </>; ```
This commit is contained in:
parent
d42a90cf4f
commit
2e4db3344f
|
|
@ -1553,7 +1553,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||||
expect(tree[0].id).toEqual(0);
|
expect(tree[0].id).toEqual(0);
|
||||||
expect(tree[0].isStateEditable).toEqual(false);
|
expect(tree[0].isStateEditable).toEqual(false);
|
||||||
expect(tree[0].name).toEqual('Id');
|
expect(tree[0].name).toEqual('Id');
|
||||||
expect(String(tree[0].value).startsWith(':r')).toBe(true);
|
expect(String(tree[0].value).startsWith('\u00ABr')).toBe(true);
|
||||||
|
|
||||||
expect(normalizeSourceLoc(tree)[1]).toMatchInlineSnapshot(`
|
expect(normalizeSourceLoc(tree)[1]).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -858,7 +858,7 @@ export function makeId(
|
||||||
): string {
|
): string {
|
||||||
const idPrefix = resumableState.idPrefix;
|
const idPrefix = resumableState.idPrefix;
|
||||||
|
|
||||||
let id = ':' + idPrefix + 'R' + treeId;
|
let id = '\u00AB' + idPrefix + 'R' + treeId;
|
||||||
|
|
||||||
// Unless this is the first id at this level, append a number at the end
|
// Unless this is the first id at this level, append a number at the end
|
||||||
// that represents the position of this useId hook among all the useId
|
// that represents the position of this useId hook among all the useId
|
||||||
|
|
@ -867,7 +867,7 @@ export function makeId(
|
||||||
id += 'H' + localId.toString(32);
|
id += 'H' + localId.toString(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
return id + ':';
|
return id + '\u00BB';
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeHTMLTextNode(text: string): string {
|
function encodeHTMLTextNode(text: string): string {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ describe('useId', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeTreeIdForTesting(id) {
|
function normalizeTreeIdForTesting(id) {
|
||||||
const result = id.match(/:(R|r)([a-z0-9]*)(H([0-9]*))?:/);
|
const result = id.match(/\u00AB(R|r)([a-z0-9]*)(H([0-9]*))?\u00BB/);
|
||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
throw new Error('Invalid id format');
|
throw new Error('Invalid id format');
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +285,7 @@ describe('useId', () => {
|
||||||
// 'R:' prefix, and the first character after that, which may not correspond
|
// 'R:' prefix, and the first character after that, which may not correspond
|
||||||
// to a complete set of 5 bits.
|
// to a complete set of 5 bits.
|
||||||
//
|
//
|
||||||
// Example: :Rclalalalalalalala...:
|
// Example: «Rclalalalalalalala...:
|
||||||
//
|
//
|
||||||
// We can use this pattern to test large ids that exceed the bitwise
|
// We can use this pattern to test large ids that exceed the bitwise
|
||||||
// safe range (32 bits). The algorithm should theoretically support ids
|
// safe range (32 bits). The algorithm should theoretically support ids
|
||||||
|
|
@ -320,8 +320,8 @@ describe('useId', () => {
|
||||||
|
|
||||||
// Confirm that every id matches the expected pattern
|
// Confirm that every id matches the expected pattern
|
||||||
for (let i = 0; i < divs.length; i++) {
|
for (let i = 0; i < divs.length; i++) {
|
||||||
// Example: :Rclalalalalalalala...:
|
// Example: «Rclalalalalalalala...:
|
||||||
expect(divs[i].id).toMatch(/^:R.(((al)*a?)((la)*l?))*:$/);
|
expect(divs[i].id).toMatch(/^\u00ABR.(((al)*a?)((la)*l?))*\u00BB$/);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -345,7 +345,7 @@ describe('useId', () => {
|
||||||
<div
|
<div
|
||||||
id="container"
|
id="container"
|
||||||
>
|
>
|
||||||
:R0:, :R0H1:, :R0H2:
|
«R0», «R0H1», «R0H2»
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
@ -370,7 +370,7 @@ describe('useId', () => {
|
||||||
<div
|
<div
|
||||||
id="container"
|
id="container"
|
||||||
>
|
>
|
||||||
:R0:
|
«R0»
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
@ -608,10 +608,10 @@ describe('useId', () => {
|
||||||
id="container"
|
id="container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
:custom-prefix-R1:
|
«custom-prefix-R1»
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
:custom-prefix-R2:
|
«custom-prefix-R2»
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
@ -625,13 +625,13 @@ describe('useId', () => {
|
||||||
id="container"
|
id="container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
:custom-prefix-R1:
|
«custom-prefix-R1»
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
:custom-prefix-R2:
|
«custom-prefix-R2»
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
:custom-prefix-r0:
|
«custom-prefix-r0»
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
@ -672,11 +672,11 @@ describe('useId', () => {
|
||||||
id="container"
|
id="container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
:R0:
|
«R0»
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
:R7:
|
«R7»
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -690,11 +690,11 @@ describe('useId', () => {
|
||||||
id="container"
|
id="container"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
:R0:
|
«R0»
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
:R7:
|
«R7»
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
11
packages/react-reconciler/src/ReactFiberHooks.js
vendored
11
packages/react-reconciler/src/ReactFiberHooks.js
vendored
|
|
@ -3595,7 +3595,7 @@ function mountId(): string {
|
||||||
const treeId = getTreeId();
|
const treeId = getTreeId();
|
||||||
|
|
||||||
// Use a captial R prefix for server-generated ids.
|
// Use a captial R prefix for server-generated ids.
|
||||||
id = ':' + identifierPrefix + 'R' + treeId;
|
id = '\u00AB' + identifierPrefix + 'R' + treeId;
|
||||||
|
|
||||||
// Unless this is the first id at this level, append a number at the end
|
// Unless this is the first id at this level, append a number at the end
|
||||||
// that represents the position of this useId hook among all the useId
|
// that represents the position of this useId hook among all the useId
|
||||||
|
|
@ -3605,11 +3605,16 @@ function mountId(): string {
|
||||||
id += 'H' + localId.toString(32);
|
id += 'H' + localId.toString(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
id += ':';
|
id += '\u00BB';
|
||||||
} else {
|
} else {
|
||||||
// Use a lowercase r prefix for client-generated ids.
|
// Use a lowercase r prefix for client-generated ids.
|
||||||
const globalClientId = globalClientIdCounter++;
|
const globalClientId = globalClientIdCounter++;
|
||||||
id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
|
id =
|
||||||
|
'\u00AB' +
|
||||||
|
identifierPrefix +
|
||||||
|
'r' +
|
||||||
|
globalClientId.toString(32) +
|
||||||
|
'\u00BB';
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.memoizedState = id;
|
hook.memoizedState = id;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user