@iyulab/components 1.37.1 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/utilities/Locale.d.ts +1 -1
- package/dist/utilities/Locale.js +13 -3
- package/dist/utilities/format.d.ts +7 -2
- package/dist/utilities/format.js +7 -2
- package/package.json +1 -1
- package/skills/iyulab-components/SKILL.md +1 -0
- package/skills/iyulab-components/references/utilities/browser-storage.md +5 -0
- package/skills/iyulab-components/references/utilities/elements.md +17 -0
- package/skills/iyulab-components/references/utilities/format.md +73 -0
- package/skills/iyulab-components/references/utilities/locale.md +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.38.0] - 2026-09-08
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`<html lang>` now takes precedence over the browser language when the initial
|
|
8
|
+
locale is detected.** `detectLocale()` consulted `navigator.language` first, which
|
|
9
|
+
made the `document.documentElement.lang` branch unreachable in a browser: a document
|
|
10
|
+
declaring `lang="en"` still emitted accessible names in the browser's language, and a
|
|
11
|
+
screen reader then announced them under English pronunciation rules (WCAG 3.1.1 /
|
|
12
|
+
3.1.2). The `lang` attribute is the author's declaration of the document's language;
|
|
13
|
+
the browser language is the fallback for when that declaration is absent, not an
|
|
14
|
+
override for it. **Only documents that actually set `lang` change behaviour** — an
|
|
15
|
+
absent or empty `lang` falls back to the browser language exactly as before, so apps
|
|
16
|
+
that never set it are unaffected. `Locale.set()` still overrides both. The Node 21+
|
|
17
|
+
guard on the `navigator` branch is unchanged.
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
|
|
21
|
+
- `formatNumber`, `formatCurrency` and `formatDate` were public through the barrel but
|
|
22
|
+
appeared in no published document; they are now documented.
|
|
23
|
+
- `isCoarsePointer` is documented alongside the other element helpers.
|
|
24
|
+
- `formatDate`'s JSDoc cross-referenced a `formatCurrency` fallback that does not exist.
|
|
25
|
+
`formatCurrency` throws on an invalid currency code, and the asymmetry between the two
|
|
26
|
+
is deliberate; both are now described as they behave.
|
|
27
|
+
- The `BrowserStorage` reference notes that the constructor throws outside a browser.
|
|
28
|
+
|
|
3
29
|
## [1.37.1] - 2026-09-07
|
|
4
30
|
|
|
5
31
|
### Fixed
|
|
@@ -47,7 +47,7 @@ export declare class Locale {
|
|
|
47
47
|
private constructor();
|
|
48
48
|
/** 전역 활성 로케일을 지정합니다. */
|
|
49
49
|
static set(locale: LocaleTag): void;
|
|
50
|
-
/** 전역 활성 로케일을 반환합니다 (초기값은 브라우저 언어 자동 감지, 실패 시 'en'). */
|
|
50
|
+
/** 전역 활성 로케일을 반환합니다 (초기값은 `<html lang>` → 브라우저 언어 자동 감지, 실패 시 'en'). */
|
|
51
51
|
static get(): LocaleTag;
|
|
52
52
|
/**
|
|
53
53
|
* 로케일 하나의 메시지 테이블(전체 또는 일부)을 등록합니다.
|
package/dist/utilities/Locale.js
CHANGED
|
@@ -32,10 +32,20 @@ var builtins = new Map(Object.entries(/* #__PURE__ */ Object.assign({
|
|
|
32
32
|
return [(path.split("/").pop()?.replace(".json", "") || "").toLowerCase(), mod];
|
|
33
33
|
}));
|
|
34
34
|
var overrides = /* @__PURE__ */ new Map();
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* 초기 로케일을 `document.documentElement.lang` → `navigator.language` → `'en'` 순으로 추측한다.
|
|
37
|
+
*
|
|
38
|
+
* ⚠**순서가 계약이다.** `<html lang>` 은 HTML 명세상 **문서 언어에 대한 저자의 선언**이고,
|
|
39
|
+
* 보조기술은 그것으로 발음 규칙을 고른다(WCAG 3.1.1 Language of Page / 3.1.2 Language of
|
|
40
|
+
* Parts). `navigator.language` 는 그 선언이 **없을 때**의 사용자 선호 폴백이지 저자 선언을
|
|
41
|
+
* 덮어쓸 근거가 아니다 — 덮어쓰면 `lang="en"` 문서가 다른 언어의 접근성 이름을 내보내고,
|
|
42
|
+
* 스크린리더는 그것을 영어 발음 규칙으로 읽으려 한다.
|
|
43
|
+
*
|
|
44
|
+
* 자동 감지가 맞지 않는 앱은 `Locale.set()` 으로 언제든 덮어쓸 수 있다.
|
|
45
|
+
*/
|
|
36
46
|
function detectLocale() {
|
|
37
|
-
if (typeof window !== "undefined" && typeof navigator !== "undefined" && navigator.language) return navigator.language;
|
|
38
47
|
if (typeof document !== "undefined" && document.documentElement?.lang) return document.documentElement.lang;
|
|
48
|
+
if (typeof window !== "undefined" && typeof navigator !== "undefined" && navigator.language) return navigator.language;
|
|
39
49
|
return "en";
|
|
40
50
|
}
|
|
41
51
|
var active = detectLocale();
|
|
@@ -72,7 +82,7 @@ var Locale = class {
|
|
|
72
82
|
static set(locale) {
|
|
73
83
|
active = locale;
|
|
74
84
|
}
|
|
75
|
-
/** 전역 활성 로케일을 반환합니다 (초기값은 브라우저 언어 자동 감지, 실패 시 'en'). */
|
|
85
|
+
/** 전역 활성 로케일을 반환합니다 (초기값은 `<html lang>` → 브라우저 언어 자동 감지, 실패 시 'en'). */
|
|
76
86
|
static get() {
|
|
77
87
|
return active;
|
|
78
88
|
}
|
|
@@ -19,7 +19,12 @@ export declare function formatCurrency(value: number, currency: string, options?
|
|
|
19
19
|
* already-Invalid `Date`) degrades to `String(value)` rather than throwing —
|
|
20
20
|
* `Intl.DateTimeFormat.format()` throws `RangeError` on an Invalid Date, and this
|
|
21
21
|
* utility is called from render paths where an uncaught throw blanks the whole
|
|
22
|
-
* component.
|
|
23
|
-
*
|
|
22
|
+
* component.
|
|
23
|
+
*
|
|
24
|
+
* ⚠This degrade is **deliberately not symmetric** with {@link formatCurrency}, which
|
|
25
|
+
* throws `RangeError` on an invalid currency code. A date arrives as *data* — from an
|
|
26
|
+
* API, a user, a stale cache — so a bad one is an expected runtime state. A currency
|
|
27
|
+
* code is written by the developer at the call site, so a bad one is a bug that should
|
|
28
|
+
* surface at the first render rather than be papered over with a wrong-looking amount.
|
|
24
29
|
*/
|
|
25
30
|
export declare function formatDate(value: Date | string, options?: Intl.DateTimeFormatOptions, locale?: LocaleTag): string;
|
package/dist/utilities/format.js
CHANGED
|
@@ -49,8 +49,13 @@ function formatCurrency(value, currency, options, locale) {
|
|
|
49
49
|
* already-Invalid `Date`) degrades to `String(value)` rather than throwing —
|
|
50
50
|
* `Intl.DateTimeFormat.format()` throws `RangeError` on an Invalid Date, and this
|
|
51
51
|
* utility is called from render paths where an uncaught throw blanks the whole
|
|
52
|
-
* component.
|
|
53
|
-
*
|
|
52
|
+
* component.
|
|
53
|
+
*
|
|
54
|
+
* ⚠This degrade is **deliberately not symmetric** with {@link formatCurrency}, which
|
|
55
|
+
* throws `RangeError` on an invalid currency code. A date arrives as *data* — from an
|
|
56
|
+
* API, a user, a stale cache — so a bad one is an expected runtime state. A currency
|
|
57
|
+
* code is written by the developer at the call site, so a bad one is a bug that should
|
|
58
|
+
* surface at the first render rather than be papered over with a wrong-looking amount.
|
|
54
59
|
*/
|
|
55
60
|
function formatDate(value, options, locale) {
|
|
56
61
|
const date = resolve(value);
|
package/package.json
CHANGED
|
@@ -126,6 +126,7 @@ import { UButton, UInput } from '@iyulab/components/react';
|
|
|
126
126
|
- [`converters`](./references/utilities/converters.md) — Lit property attribute converters (array, JSON, date, url…)
|
|
127
127
|
- [`Locale`](./references/utilities/locale.md) — Validation-message locale registry and lookup utility
|
|
128
128
|
- [`elements`](./references/utilities/elements.md) — Shadow-DOM-aware DOM query helpers
|
|
129
|
+
- [`format`](./references/utilities/format.md) — Locale-aware number, currency and date formatting
|
|
129
130
|
- [`OverlayManager`](./references/utilities/overlay-manager.md) — Internal overlay stack and z-index manager
|
|
130
131
|
|
|
131
132
|
---
|
|
@@ -11,6 +11,11 @@ Unified key-value storage API over `localStorage` or browser cookies.
|
|
|
11
11
|
writing. `new BrowserStorage({ type: 'cookie' })` throws `"Cookies are not supported in this
|
|
12
12
|
browser."` where it's unavailable; `localStorage` has no such restriction.
|
|
13
13
|
|
|
14
|
+
⚠ The constructor also throws `"BrowserStorage can only be used in a browser environment."`
|
|
15
|
+
when there is no `window` at all. Construct it inside a browser-only path (an event handler,
|
|
16
|
+
`connectedCallback`, an effect) rather than at module scope, or an SSR/prerender build fails
|
|
17
|
+
on import alone.
|
|
18
|
+
|
|
14
19
|
## Usage
|
|
15
20
|
|
|
16
21
|
```ts
|
|
@@ -37,3 +37,20 @@ Same as `querySelectorWithin` but returns all matches.
|
|
|
37
37
|
```ts
|
|
38
38
|
const inputs = querySelectorAllWithin(this, 'u-input');
|
|
39
39
|
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### `isCoarsePointer(event)`
|
|
44
|
+
|
|
45
|
+
Returns `true` for a pointer with no lasting hover state — touch and pen.
|
|
46
|
+
|
|
47
|
+
Such a pointer fires `pointerleave` immediately after `pointerenter` on a tap, so a
|
|
48
|
+
hover-triggered surface that does not check this opens and closes in the same gesture.
|
|
49
|
+
Branch on it before treating `pointerenter` as "the user is hovering here".
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
private onPointerEnter(e: PointerEvent) {
|
|
53
|
+
if (isCoarsePointer(e)) return; // let the click/tap handler own this instead
|
|
54
|
+
this.open = true;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# format
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { formatNumber, formatCurrency, formatDate } from '@iyulab/components';
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Thin wrappers over `Intl.NumberFormat` / `Intl.DateTimeFormat` that default to the
|
|
8
|
+
library's active locale (`Locale.get()`), so numbers and dates rendered by your app match
|
|
9
|
+
the ones the components render themselves.
|
|
10
|
+
|
|
11
|
+
They add no formatting rules of their own — everything `Intl` accepts is passed straight
|
|
12
|
+
through. Reach for them instead of calling `Intl` directly when you want the active locale
|
|
13
|
+
applied without threading it through every call site.
|
|
14
|
+
|
|
15
|
+
## Functions
|
|
16
|
+
|
|
17
|
+
### `formatNumber(value, options?, locale?)`
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
formatNumber(1234.5); // active locale
|
|
21
|
+
formatNumber(0.42, { style: 'percent' }); // '42%'
|
|
22
|
+
formatNumber(1234.5, { maximumFractionDigits: 0 }, 'de-DE');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`options` is `Intl.NumberFormatOptions`. `locale` overrides the active locale for this call
|
|
26
|
+
only.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### `formatCurrency(value, currency, options?, locale?)`
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
formatCurrency(1234.5, 'USD'); // '$1,234.50' in an en locale
|
|
34
|
+
formatCurrency(1234.5, 'KRW');
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`currency` is **required and has no default** — which currency an amount is in is domain
|
|
38
|
+
knowledge this utility will not guess.
|
|
39
|
+
|
|
40
|
+
⚠ An invalid currency code throws `RangeError` rather than degrading (see the note under
|
|
41
|
+
`formatDate`). Pass a valid ISO 4217 code.
|
|
42
|
+
|
|
43
|
+
If `options` contains `currency` or `style`, those win over the `currency` argument.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### `formatDate(value, options?, locale?)`
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
formatDate(new Date());
|
|
51
|
+
formatDate('2026-03-14', { dateStyle: 'long' });
|
|
52
|
+
formatDate(order.createdAt, { dateStyle: 'short', timeStyle: 'short' });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Accepts a `Date` or an ISO `YYYY-MM-DD` string. **The string form is parsed as local time,
|
|
56
|
+
not UTC** — `'2026-03-14'` is midnight where the user is, so a date never shifts a day
|
|
57
|
+
across time zones the way `new Date('2026-03-14')` does.
|
|
58
|
+
|
|
59
|
+
A value that cannot be resolved to a real date — a malformed string, or an already-invalid
|
|
60
|
+
`Date` — returns `String(value)` instead of throwing. `Intl.DateTimeFormat.format()` throws
|
|
61
|
+
`RangeError` on an invalid date, and this runs inside render paths where an uncaught throw
|
|
62
|
+
blanks the whole component; showing the raw value is the lesser failure.
|
|
63
|
+
|
|
64
|
+
That degrade is **deliberately not symmetric** with `formatCurrency`: a date arrives as
|
|
65
|
+
data (an API, a user, a stale cache), so a bad one is an expected runtime state; a currency
|
|
66
|
+
code is written at the call site, so a bad one is a bug worth surfacing.
|
|
67
|
+
|
|
68
|
+
## Locale
|
|
69
|
+
|
|
70
|
+
All three read `Locale.get()` when `locale` is omitted, so they follow whatever
|
|
71
|
+
[`Locale`](./locale.md) resolved — `<html lang>` first, then the browser language. Pass
|
|
72
|
+
`locale` explicitly only when one value must be formatted differently from the rest of the
|
|
73
|
+
page (a currency shown in its home locale, for example).
|
|
@@ -10,7 +10,11 @@ Locale registry utility for library-generated validation messages.
|
|
|
10
10
|
|
|
11
11
|
Built in: `en`, `ko`, `ja`, `zh-CN`, `zh-TW`, `es`, `fr`, `de`, `pt-BR`, `vi`, `th`, `id`, `ru`, `ar`.
|
|
12
12
|
|
|
13
|
-
Initial locale is auto-detected from `
|
|
13
|
+
Initial locale is auto-detected from `document.documentElement.lang` first, then
|
|
14
|
+
`navigator.language`, with English fallback. `<html lang>` wins because it is the author's
|
|
15
|
+
declaration of the document's language and is what assistive technology uses to pick
|
|
16
|
+
pronunciation rules (WCAG 3.1.1 / 3.1.2); the browser language is the user-preference
|
|
17
|
+
fallback for when no such declaration exists. Call `Locale.set()` to override either.
|
|
14
18
|
|
|
15
19
|
## API
|
|
16
20
|
|