From ccd68e0c1421da6a56ac9865e525d828ccb7dcda Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Thu, 16 Jan 2025 11:59:54 -0800 Subject: [PATCH 01/14] Declare `relative-element` under React.JSX to fix React 19 compatibility Fixes https://github.com/github/relative-time-element/issues/304 I had to add a `@ts-expect-error` comment since otherwise the typechecker would complain about missing `react` as a dependency. --- src/relative-time-element-define.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/relative-time-element-define.ts b/src/relative-time-element-define.ts index 34e5a59..b130fda 100644 --- a/src/relative-time-element-define.ts +++ b/src/relative-time-element-define.ts @@ -22,6 +22,10 @@ declare global { interface HTMLElementTagNameMap { 'relative-time': RelativeTimeElement } +} + +// @ts-expect-error This is needed for consumers using React 19 and above but TypeScript complains because `react` isn't a dependency of the project. +declare module 'react' { namespace JSX { interface IntrinsicElements { ['relative-time']: JSXBase['span'] & Partial> From 755e2e6bda079115b3e66c45c437b52c8f6b027f Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Fri, 17 Jan 2025 08:58:58 -0800 Subject: [PATCH 02/14] add @types/react as peerDep --- package-lock.json | 28 ++++++++++++++++++++++++++++ package.json | 3 +++ src/relative-time-element-define.ts | 1 - 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 49ca1dd..361689d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,9 @@ "eslint-plugin-custom-elements": "^0.0.8", "eslint-plugin-github": "^4.7.0", "typescript": "^5.0.4" + }, + "peerDependencies": { + "@types/react": "18 || 19" } }, "node_modules/@babel/code-frame": { @@ -902,6 +905,13 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT", + "peer": true + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -914,6 +924,17 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "node_modules/@types/react": { + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -2693,6 +2714,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT", + "peer": true + }, "node_modules/custom-elements-manifest": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", diff --git a/package.json b/package.json index 628a903..de71e87 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,9 @@ "eslint-plugin-github": "^4.7.0", "typescript": "^5.0.4" }, + "peerDependencies": { + "@types/react": "18 || 19" + }, "eslintIgnore": [ "dist/" ], diff --git a/src/relative-time-element-define.ts b/src/relative-time-element-define.ts index b130fda..47c3f91 100644 --- a/src/relative-time-element-define.ts +++ b/src/relative-time-element-define.ts @@ -24,7 +24,6 @@ declare global { } } -// @ts-expect-error This is needed for consumers using React 19 and above but TypeScript complains because `react` isn't a dependency of the project. declare module 'react' { namespace JSX { interface IntrinsicElements { From 812968de38ed82d350c83ab3fa6d3acb1516d95a Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Fri, 17 Jan 2025 09:53:46 -0800 Subject: [PATCH 03/14] Update package.json Co-authored-by: Keith Cirkel --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index de71e87..4f06073 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,11 @@ "peerDependencies": { "@types/react": "18 || 19" }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + }, "eslintIgnore": [ "dist/" ], From 572c2124f3ca1002a6597dfa55cea88182a1dc7e Mon Sep 17 00:00:00 2001 From: Marie Lucca <40550942+francinelucca@users.noreply.github.com> Date: Tue, 6 May 2025 10:44:58 -0400 Subject: [PATCH 04/14] Revert "Declare `relative-element` under React.JSX to fix React 19 compatibility" --- package-lock.json | 28 ---------------------------- package.json | 8 -------- src/relative-time-element-define.ts | 3 --- 3 files changed, 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 280b423..b9755e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,9 +21,6 @@ "eslint-plugin-custom-elements": "^0.0.8", "eslint-plugin-github": "^4.7.0", "typescript": "^5.0.4" - }, - "peerDependencies": { - "@types/react": "18 || 19" } }, "node_modules/@babel/code-frame": { @@ -1289,13 +1286,6 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "license": "MIT", - "peer": true - }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -1308,17 +1298,6 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, - "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -3535,13 +3514,6 @@ "node": ">= 8" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true - }, "node_modules/custom-elements-manifest": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", diff --git a/package.json b/package.json index d1f4f67..2343dd6 100644 --- a/package.json +++ b/package.json @@ -45,14 +45,6 @@ "eslint-plugin-github": "^4.7.0", "typescript": "^5.0.4" }, - "peerDependencies": { - "@types/react": "18 || 19" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - }, "eslintIgnore": [ "dist/" ], diff --git a/src/relative-time-element-define.ts b/src/relative-time-element-define.ts index 47c3f91..34e5a59 100644 --- a/src/relative-time-element-define.ts +++ b/src/relative-time-element-define.ts @@ -22,9 +22,6 @@ declare global { interface HTMLElementTagNameMap { 'relative-time': RelativeTimeElement } -} - -declare module 'react' { namespace JSX { interface IntrinsicElements { ['relative-time']: JSXBase['span'] & Partial> From f1c6b2ebc72689efa30dffe7657b72305a9ae628 Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Fri, 2 May 2025 10:19:19 -0400 Subject: [PATCH 05/14] Support aria-hidden --- examples/index.html | 6 ++++++ src/relative-time-element.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/index.html b/examples/index.html index 13d91ba..6ea08e2 100644 --- a/examples/index.html +++ b/examples/index.html @@ -200,6 +200,12 @@

Localised Dates

+

+ With aria-hidden + +

+ + + diff --git a/src/relative-time-element.ts b/src/relative-time-element.ts index 3de41de..e3f3c0a 100644 --- a/src/relative-time-element.ts +++ b/src/relative-time-element.ts @@ -112,6 +112,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor 'datetime', 'lang', 'title', + 'aria-hidden', ] } @@ -460,13 +461,16 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor if (newText) { if (this.hasAttribute('aria-hidden') && this.getAttribute('aria-hidden') === 'true') { - (this.#renderRoot as Element).innerHTML = ``; + const span = document.createElement('span') + span.setAttribute('aria-hidden', 'true') + span.textContent = newText + ;(this.#renderRoot as Element).replaceChildren(span) } else { - this.#renderRoot.textContent = newText; + this.#renderRoot.textContent = newText } } else if (this.shadowRoot === this.#renderRoot && this.textContent) { // Ensure invalid dates fall back to lightDOM text content - this.#renderRoot.textContent = this.textContent; + this.#renderRoot.textContent = this.textContent } if (newText !== oldText || newTitle !== oldTitle) { diff --git a/test/relative-time.js b/test/relative-time.js index 0d13096..d97dd55 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -122,7 +122,7 @@ suite('relative-time', function () { assert.notEqual(nextDisplay, display) }) - test('all observedAttributes have getters', async () => { + test('all observedAttributes have getters)', async () => { const members = [ ...Object.getOwnPropertyNames(RelativeTimeElement.prototype).map(n => n.replace(/([A-Z])/g, c => `-${c.toLowerCase()}`), @@ -130,6 +130,7 @@ suite('relative-time', function () { ...Object.getOwnPropertyNames(HTMLElement.prototype), ] const observedAttributes = new Set(RelativeTimeElement.observedAttributes) + observedAttributes.delete('aria-hidden') // Standard HTML attribute, no need for custom getter for (const member of members) observedAttributes.delete(member) assert.empty([...observedAttributes], 'observedAttributes that arent class members') }) @@ -1872,6 +1873,40 @@ suite('relative-time', function () { } }) + suite('[aria-hidden]', async () => { + test('[aria-hidden="true"] applies to shadow root', async () => { + const now = new Date().toISOString() + const time = document.createElement('relative-time') + time.setAttribute('datetime', now) + time.setAttribute('aria-hidden', 'true') + await Promise.resolve() + + const span = time.shadowRoot.querySelector('span') + assert.equal(span.getAttribute('aria-hidden'), 'true') + }) + + test('[aria-hidden="false"] applies to shadow root', async () => { + const now = new Date().toISOString() + const time = document.createElement('relative-time') + time.setAttribute('datetime', now) + time.setAttribute('aria-hidden', 'false') + await Promise.resolve() + + assert.isNull(time.querySelector('[aria-hidden]'), 'Expected no aria-hidden to be present') + assert.isNull(time.querySelector('span'), 'Expected no span to be present') + }) + + test('no aria-hidden applies to shadow root', async () => { + const now = new Date().toISOString() + const time = document.createElement('relative-time') + time.setAttribute('datetime', now) + await Promise.resolve() + + assert.isNull(time.querySelector('[aria-hidden]'), 'Expected no aria-hidden to be present') + assert.isNull(time.querySelector('span'), 'Expected no span to be present') + }) + }) + suite('legacy formats', function () { const referenceDate = '2022-10-24T14:46:00.000Z' const tests = new Set([ From 7cf18282d5105f9fe947738f75625d377253e856 Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Wed, 7 May 2025 11:32:04 -0400 Subject: [PATCH 07/14] add example --- test/relative-time.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/relative-time.js b/test/relative-time.js index d97dd55..937798f 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -122,7 +122,7 @@ suite('relative-time', function () { assert.notEqual(nextDisplay, display) }) - test('all observedAttributes have getters)', async () => { + test('all observedAttributes have getters', async () => { const members = [ ...Object.getOwnPropertyNames(RelativeTimeElement.prototype).map(n => n.replace(/([A-Z])/g, c => `-${c.toLowerCase()}`), From 63575190bcfc78d1d636d51bff820b4f723d91ee Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Wed, 7 May 2025 11:35:05 -0400 Subject: [PATCH 08/14] Fix issues --- examples/index.html | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/index.html b/examples/index.html index 956c3c8..d02171a 100644 --- a/examples/index.html +++ b/examples/index.html @@ -200,17 +200,17 @@

Localised Dates

- - - + + + From e50de7fa8a75aa827daa543c1a6e803d2d7a5ca4 Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Wed, 7 May 2025 11:57:59 -0400 Subject: [PATCH 09/14] update render root content --- src/relative-time-element.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/relative-time-element.ts b/src/relative-time-element.ts index e3f3c0a..80dc977 100644 --- a/src/relative-time-element.ts +++ b/src/relative-time-element.ts @@ -202,6 +202,17 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor return `${this.prefix} ${formatter.format(date)}`.trim() } + #updateRenderRootContent(content: string | null): void { + if (this.hasAttribute('aria-hidden') && this.getAttribute('aria-hidden') === 'true') { + const span = document.createElement('span') + span.setAttribute('aria-hidden', 'true') + span.textContent = content + ;(this.#renderRoot as Element).replaceChildren(span) + } else { + this.#renderRoot.textContent = content + } + } + #onRelativeTimeUpdated: ((event: RelativeTimeUpdatedEvent) => void) | null = null get onRelativeTimeUpdated() { return this.#onRelativeTimeUpdated @@ -460,17 +471,10 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor } if (newText) { - if (this.hasAttribute('aria-hidden') && this.getAttribute('aria-hidden') === 'true') { - const span = document.createElement('span') - span.setAttribute('aria-hidden', 'true') - span.textContent = newText - ;(this.#renderRoot as Element).replaceChildren(span) - } else { - this.#renderRoot.textContent = newText - } + this.#updateRenderRootContent(newText) } else if (this.shadowRoot === this.#renderRoot && this.textContent) { // Ensure invalid dates fall back to lightDOM text content - this.#renderRoot.textContent = this.textContent + this.#updateRenderRootContent(this.textContent) } if (newText !== oldText || newTitle !== oldTitle) { From 9efd32a8e9349ccf4b6c494b7f7714b15698ebe4 Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Wed, 7 May 2025 12:43:08 -0400 Subject: [PATCH 10/14] Toggle aria-hidden --- examples/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/index.html b/examples/index.html index d02171a..886af5c 100644 --- a/examples/index.html +++ b/examples/index.html @@ -200,6 +200,8 @@

Localised Dates

+

With Aria Hidden

+