Skip to content

Commit f5dd512

Browse files
silverwindclaude
andcommitted
Add hour-cycle to observedAttributes and add tests
Add hour-cycle to observedAttributes so dynamic attribute changes trigger re-renders. Add test suite covering all four hour cycle values, title formatting, ancestor inheritance, attribute override precedence, and dynamic re-rendering. Fix existing locale test to use explicit hour-cycle attribute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a2d1259 commit f5dd512

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

src/relative-time-element.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
142142
'title',
143143
'aria-hidden',
144144
'time-zone',
145+
'hour-cycle',
145146
]
146147
}
147148

test/relative-time.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,7 @@ suite('relative-time', function () {
19821982
const el = document.createElement('relative-time')
19831983
el.setAttribute('lang', 'es-ES')
19841984
el.setAttribute('time-zone', 'Europe/Madrid')
1985+
el.setAttribute('hour-cycle', 'h23')
19851986

19861987
el.setAttribute('datetime', '2023-01-15T17:00:00.000Z')
19871988
await Promise.resolve()
@@ -2815,6 +2816,134 @@ suite('relative-time', function () {
28152816
}
28162817
})
28172818

2819+
suite('[hourCycle]', function () {
2820+
test('formats with 24-hour cycle when hour-cycle is h23', async () => {
2821+
const el = document.createElement('relative-time')
2822+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2823+
el.setAttribute('time-zone', 'UTC')
2824+
el.setAttribute('format', 'datetime')
2825+
el.setAttribute('hour', 'numeric')
2826+
el.setAttribute('minute', '2-digit')
2827+
el.setAttribute('hour-cycle', 'h23')
2828+
await Promise.resolve()
2829+
assert.notMatch(el.shadowRoot.textContent, /AM|PM/i)
2830+
assert.match(el.shadowRoot.textContent, /15:00/)
2831+
})
2832+
2833+
test('formats with 12-hour cycle when hour-cycle is h12', async () => {
2834+
const el = document.createElement('relative-time')
2835+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2836+
el.setAttribute('time-zone', 'UTC')
2837+
el.setAttribute('format', 'datetime')
2838+
el.setAttribute('hour', 'numeric')
2839+
el.setAttribute('minute', '2-digit')
2840+
el.setAttribute('hour-cycle', 'h12')
2841+
await Promise.resolve()
2842+
assert.match(el.shadowRoot.textContent, /3:00/)
2843+
assert.match(el.shadowRoot.textContent, /PM/i)
2844+
})
2845+
2846+
test('formats with 12-hour cycle when hour-cycle is h11', async () => {
2847+
const el = document.createElement('relative-time')
2848+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2849+
el.setAttribute('time-zone', 'UTC')
2850+
el.setAttribute('format', 'datetime')
2851+
el.setAttribute('hour', 'numeric')
2852+
el.setAttribute('minute', '2-digit')
2853+
el.setAttribute('hour-cycle', 'h11')
2854+
await Promise.resolve()
2855+
assert.match(el.shadowRoot.textContent, /3:00/)
2856+
assert.match(el.shadowRoot.textContent, /PM/i)
2857+
})
2858+
2859+
test('formats with 24-hour cycle when hour-cycle is h24', async () => {
2860+
const el = document.createElement('relative-time')
2861+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2862+
el.setAttribute('time-zone', 'UTC')
2863+
el.setAttribute('format', 'datetime')
2864+
el.setAttribute('hour', 'numeric')
2865+
el.setAttribute('minute', '2-digit')
2866+
el.setAttribute('hour-cycle', 'h24')
2867+
await Promise.resolve()
2868+
assert.notMatch(el.shadowRoot.textContent, /AM|PM/i)
2869+
})
2870+
2871+
test('title uses hour-cycle setting', async () => {
2872+
const el = document.createElement('relative-time')
2873+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2874+
el.setAttribute('time-zone', 'UTC')
2875+
el.setAttribute('hour-cycle', 'h23')
2876+
await Promise.resolve()
2877+
assert.notMatch(el.getAttribute('title'), /AM|PM/i)
2878+
assert.match(el.getAttribute('title'), /15/)
2879+
})
2880+
2881+
test('inherits hour-cycle from ancestor', async () => {
2882+
const el = document.createElement('relative-time')
2883+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2884+
el.setAttribute('time-zone', 'UTC')
2885+
el.setAttribute('format', 'datetime')
2886+
el.setAttribute('hour', 'numeric')
2887+
el.setAttribute('minute', '2-digit')
2888+
const div = document.createElement('div')
2889+
div.setAttribute('hour-cycle', 'h23')
2890+
div.appendChild(el)
2891+
document.body.appendChild(div)
2892+
await Promise.resolve()
2893+
assert.notMatch(el.shadowRoot.textContent, /AM|PM/i)
2894+
assert.match(el.shadowRoot.textContent, /15:00/)
2895+
div.remove()
2896+
})
2897+
2898+
test('inherits hour-cycle from documentElement', async () => {
2899+
const el = document.createElement('relative-time')
2900+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2901+
el.setAttribute('time-zone', 'UTC')
2902+
el.setAttribute('format', 'datetime')
2903+
el.setAttribute('hour', 'numeric')
2904+
el.setAttribute('minute', '2-digit')
2905+
document.documentElement.setAttribute('hour-cycle', 'h23')
2906+
await Promise.resolve()
2907+
assert.notMatch(el.shadowRoot.textContent, /AM|PM/i)
2908+
assert.match(el.shadowRoot.textContent, /15:00/)
2909+
document.documentElement.removeAttribute('hour-cycle')
2910+
})
2911+
2912+
test('element attribute overrides ancestor', async () => {
2913+
const el = document.createElement('relative-time')
2914+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2915+
el.setAttribute('time-zone', 'UTC')
2916+
el.setAttribute('format', 'datetime')
2917+
el.setAttribute('hour', 'numeric')
2918+
el.setAttribute('minute', '2-digit')
2919+
el.setAttribute('hour-cycle', 'h12')
2920+
const div = document.createElement('div')
2921+
div.setAttribute('hour-cycle', 'h23')
2922+
div.appendChild(el)
2923+
document.body.appendChild(div)
2924+
await Promise.resolve()
2925+
assert.match(el.shadowRoot.textContent, /3:00/)
2926+
assert.match(el.shadowRoot.textContent, /PM/i)
2927+
div.remove()
2928+
})
2929+
2930+
test('re-renders when hour-cycle attribute changes', async () => {
2931+
const el = document.createElement('relative-time')
2932+
el.setAttribute('datetime', '2020-01-01T15:00:00.000Z')
2933+
el.setAttribute('time-zone', 'UTC')
2934+
el.setAttribute('format', 'datetime')
2935+
el.setAttribute('hour', 'numeric')
2936+
el.setAttribute('minute', '2-digit')
2937+
el.setAttribute('hour-cycle', 'h12')
2938+
await Promise.resolve()
2939+
assert.match(el.shadowRoot.textContent, /PM/i)
2940+
el.setAttribute('hour-cycle', 'h23')
2941+
await Promise.resolve()
2942+
assert.notMatch(el.shadowRoot.textContent, /AM|PM/i)
2943+
assert.match(el.shadowRoot.textContent, /15:00/)
2944+
})
2945+
})
2946+
28182947
suite('[timeZone]', function () {
28192948
test('updates when the time-zone attribute is set', async () => {
28202949
const el = document.createElement('relative-time')

0 commit comments

Comments
 (0)