uxml-preview 0.2.0 → 0.4.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 +142 -0
- package/README.md +22 -18
- package/dist/index.d.ts +28 -3
- package/dist/index.js +876 -826
- package/dist/known-divergences.d.ts +36 -0
- package/dist/model/types.d.ts +12 -1
- package/dist/render/css-map.d.ts +10 -1
- package/dist/unity-project/index.d.ts +14 -0
- package/dist/unity-project/index.js +41 -0
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,147 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-08-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- The Unity golden dumper can run from the command line, records the Unity
|
|
8
|
+
revision, editor scale, accessible font settings, and timestamp beside each
|
|
9
|
+
measurement, rejects `-nographics` and `-quit` up front, and exits non-zero
|
|
10
|
+
unless it writes exactly one JSON file per discovered case.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Relative `@import` requests are deduplicated by `(url, from)`, matching Unity's
|
|
15
|
+
parent-relative loading. Root-fixed imports (`project://…` or a path beginning
|
|
16
|
+
with `/`) are globally deduplicated, and nested `<Style>` attachments now keep
|
|
17
|
+
their Unity subtree scope. The rule comes from five Unity measurement cases,
|
|
18
|
+
including the otherwise surprising split between `Packages/...` (relative)
|
|
19
|
+
and `/Packages/...` (root-fixed).
|
|
20
|
+
|
|
21
|
+
- **Issue #1: `resolveImport` now receives a second argument,
|
|
22
|
+
`from: string | null`** —
|
|
23
|
+
the URL of the stylesheet containing the import being resolved, `null` for
|
|
24
|
+
a `<Style src="…">` reference. Without it, a relative `@import` inside an
|
|
25
|
+
imported sheet was unresolvable in principle: `a.uss` importing `"b.uss"`
|
|
26
|
+
means a path relative to `a.uss`, and the host had no way to learn `a.uss`'s
|
|
27
|
+
own URL at the point it resolved `b.uss`. `from` is always the exact string
|
|
28
|
+
this hook received as `url` for the containing sheet, never reconstructed —
|
|
29
|
+
matters most for nested imports, where it is the immediate parent, not the
|
|
30
|
+
original sheet. **Non-breaking**: existing one-argument callbacks keep
|
|
31
|
+
working unchanged, same as `resolveAsset`'s `form` argument in 0.3.0.
|
|
32
|
+
- With this, `resolveImport` joins `resolveAsset` at two positional arguments.
|
|
33
|
+
Per the "코어에 무언가를 더하기 전에" rule in CLAUDE.md, the next argument
|
|
34
|
+
either hook would need is the point to switch to an options object instead
|
|
35
|
+
of adding a third positional one.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Issue #2: inline `style="…"` asset URLs reach `resolveAsset`
|
|
40
|
+
XML-entity-decoded**,
|
|
41
|
+
matching what a `<Style src>`-loaded `.uss` file's URLs already got (that
|
|
42
|
+
file was never XML, so it never had entities to decode; an inline attribute
|
|
43
|
+
is XML text and did). `background-image: url('…&guid=…')` written inline
|
|
44
|
+
used to reach the hook as `'…&guid=…'` — wrapper quotes and
|
|
45
|
+
ampersands both literal — because `inlineDeclarations` (style/resolve.ts)
|
|
46
|
+
parses straight from the raw XML source. Fixed at `assetPath()`
|
|
47
|
+
(render/css-map.ts), immediately before the `url()`/`resource()` wrapper is
|
|
48
|
+
parsed and right before the value leaves the model for the hook — the same
|
|
49
|
+
point `<Style src>` and painted text already decode at, not at parse time
|
|
50
|
+
(decoding then would corrupt the byte-exact round-trip source slices).
|
|
51
|
+
Confirmed against two real URLs from the external corpus in
|
|
52
|
+
`ReuHomi/vscode-uxml-preview` (`examples/external`, commit `dbda387`) where
|
|
53
|
+
this caused `asset-unresolved` for 7 URLs across 2 files. Scope: only the
|
|
54
|
+
asset-URL value reaching `resolveAsset`; other inline-style property values
|
|
55
|
+
still carry undecoded entities in the model (unaffected here, since no
|
|
56
|
+
other current consumer inspects their text — `-unity-font-definition`'s
|
|
57
|
+
`resource(...)` value is only checked for presence, never read).
|
|
58
|
+
|
|
59
|
+
### Docs
|
|
60
|
+
|
|
61
|
+
- **`docs/accuracy.md`'s "which controls this covers" table now has a stated,
|
|
62
|
+
computed rule** instead of being left unverified. It never added up on its
|
|
63
|
+
own terms (17+1+6+5+1 = 30, not the stated 31) and the original counting
|
|
64
|
+
rule was never written down, so it is now defined explicitly — classify
|
|
65
|
+
`inventory` as the representative screen, then each other case by the first
|
|
66
|
+
of `<ui:ScrollView`, `<ui:Button`, `<ui:Label` its UXML contains, else
|
|
67
|
+
`VisualElement` only — and computed from `tests/golden/cases.ts` rather than
|
|
68
|
+
hand-counted. Updated numbers: `Button` 6→10, `ScrollView` 5→4 (`VisualElement`
|
|
69
|
+
only, `Label`, and the representative screen are unchanged). Mirrored into
|
|
70
|
+
`docs/accuracy.en.md`, which is not machine-checked.
|
|
71
|
+
- **The drift guard now covers this table's five numbers too**, in
|
|
72
|
+
`docs/accuracy.md` only. Same shape as the three headline figures: recomputed
|
|
73
|
+
live and failing (not skipping) if it can't find a row to check. Confirmed by
|
|
74
|
+
breaking a count and by removing a row's label, both on purpose.
|
|
75
|
+
|
|
76
|
+
## 0.3.0 — 2026-08-09
|
|
77
|
+
|
|
78
|
+
Four fixes the VSCode extension (`vscode-uxml-preview`) hit while consuming
|
|
79
|
+
this package as a dependency — not speculative additions.
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- **`KNOWN_DIVERGENCES`** gives a host something to tell apart "a limit we
|
|
84
|
+
know about" from "a bug". Three entries, and the kinds carry the
|
|
85
|
+
distinction that matters. Font metrics move with the platform, so the
|
|
86
|
+
difference cannot be reproduced here at all. Unity's rule for a wrap
|
|
87
|
+
container's height is simply not specified, so there is nothing to match
|
|
88
|
+
yet. And the Yoga vendored in UI Toolkit resolves a main-axis percentage
|
|
89
|
+
against an indefinite parent differently from the one this package depends
|
|
90
|
+
on — that one reproduces perfectly every time; it just cannot be fixed
|
|
91
|
+
from inside this repository. The 1px case is deliberately absent: that is
|
|
92
|
+
the golden tests' judgement parameter, not a limit of the renderer, and a
|
|
93
|
+
host showing it would read as a defect.
|
|
94
|
+
- **`uxml-preview/unity-project`**, a Node-only subpath exporting
|
|
95
|
+
`buildGuidIndex(projectRoot)`. Builds a GUID → absolute-path index from a
|
|
96
|
+
Unity project's `.meta` files, for the case where an asset reference's path
|
|
97
|
+
has gone stale but its GUID is still good. Skips `Library/`, `Temp/`,
|
|
98
|
+
`obj/`, `.git/`, and any `.meta` file it cannot read. Kept out of the main
|
|
99
|
+
entry point because it uses `node:fs` and the main entry must stay
|
|
100
|
+
browser-safe.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **`resolveAsset` now receives a second argument, `form: 'url' | 'resource'`**,
|
|
105
|
+
telling `url("...")` and `resource("...")` background-image references
|
|
106
|
+
apart. They resolve differently — `url()` is a path, `resource()` is a
|
|
107
|
+
Unity Resources-folder name — and a host that resolved both the same way
|
|
108
|
+
before this had no way to know which one it was looking at.
|
|
109
|
+
**Non-breaking**: existing `(path) => ...` implementations keep working
|
|
110
|
+
unchanged: `form` is an additional argument, not a replacement one.
|
|
111
|
+
|
|
112
|
+
### Fixed
|
|
113
|
+
|
|
114
|
+
- `UxmlDocument.warnings` and `RenderResult.warnings` doc comments corrected.
|
|
115
|
+
The former said it covers "malformed input" only; it also carries
|
|
116
|
+
`'import-unresolved'` for an unreadable `<Style src="…">` or `@import`. The
|
|
117
|
+
latter said it is distinct from the former on that same wrong premise.
|
|
118
|
+
Comment-only — no behavior changed.
|
|
119
|
+
|
|
120
|
+
### Docs
|
|
121
|
+
|
|
122
|
+
- **`docs/accuracy.md`'s published figures were stale**: two comparable cases
|
|
123
|
+
(`state-vs-id`, `state-vs-inline`) were merged after the doc's numbers were
|
|
124
|
+
last written, and nothing caught it. Recomputed from a live run rather than
|
|
125
|
+
trusted: **548 / 564 values (97.2%)**, **31 / 33 cases matching**, **33 / 33**
|
|
126
|
+
Unity baseline coverage — up from the stale 532/548, 29/31, 31/31. Mirrored
|
|
127
|
+
into `README.md` (both languages) and `docs/uss-vs-css.md`/`.ko.md`.
|
|
128
|
+
`docs/accuracy.en.md` was updated too but is not machine-checked (see next).
|
|
129
|
+
- **Added a regression guard**: `tests/golden/golden.test.ts` now has
|
|
130
|
+
`matches the figures published in docs/accuracy.md`, which recomputes the
|
|
131
|
+
three headline numbers from the actual case set and fails if
|
|
132
|
+
`docs/accuracy.md` says anything else — including failing (not skipping) if
|
|
133
|
+
it cannot find a row to check, so the guard can't be silently defeated by a
|
|
134
|
+
table-format change. This is the third time this specific number went stale
|
|
135
|
+
publicly (`docs/accuracy.md` itself already records the first two); the doc
|
|
136
|
+
fix alone would only prevent a third, not a fourth.
|
|
137
|
+
- Left unfixed and flagged in `docs/accuracy.md`/`accuracy.en.md`: the
|
|
138
|
+
"which controls this covers" breakdown table (`Button`/`ScrollView`/etc.
|
|
139
|
+
row counts) was already internally inconsistent before this change
|
|
140
|
+
(17+1+6+5+1 = 30, not the stated 31) and is not machine-checked. Recomputing
|
|
141
|
+
it requires a counting rule that is not written down anywhere; a guess
|
|
142
|
+
risked swapping one wrong number for another, so it was left alone and
|
|
143
|
+
called out inline instead.
|
|
144
|
+
|
|
3
145
|
## 0.2.0 — 2026-08-06
|
|
4
146
|
|
|
5
147
|
Five controls instead of three, a representative screen measured end to end
|
package/README.md
CHANGED
|
@@ -25,12 +25,14 @@ stylesheet comes out wrong.</sub>
|
|
|
25
25
|
npm install uxml-preview
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
> **v0.
|
|
29
|
-
> `
|
|
28
|
+
> **v0.2.0 — early release.** Five controls (`VisualElement`, `Label`, `Button`,
|
|
29
|
+
> `Image`, `ScrollView`), everything else drawn as a plain box with a warning,
|
|
30
|
+
> and a support matrix that says plainly what has not been measured.
|
|
30
31
|
> Read [what is not verified yet](#what-is-not-verified-yet) before relying on it.
|
|
31
32
|
>
|
|
32
|
-
> **v0.
|
|
33
|
-
>
|
|
33
|
+
> **v0.2.0 — 초기 릴리스입니다.** 컨트롤 5종(`VisualElement`, `Label`, `Button`,
|
|
34
|
+
> `Image`, `ScrollView`)이고, 나머지는 경고와 함께 일반 박스로 그립니다.
|
|
35
|
+
> 검증되지 않은 부분은 지원 범위표에 그대로 적어뒀습니다. 쓰시기 전에
|
|
34
36
|
> [아직 검증되지 않은 것](#아직-검증되지-않은-것)을 먼저 봐주세요.
|
|
35
37
|
|
|
36
38
|
---
|
|
@@ -45,8 +47,8 @@ directly in a web browser. It uses **Yoga** — the same layout engine Unity UI
|
|
|
45
47
|
itself uses — compiled to WebAssembly, so results match the Unity Editor rather than
|
|
46
48
|
approximating it.
|
|
47
49
|
|
|
48
|
-
Measured against Unity 6000.0.40f1: **
|
|
49
|
-
across
|
|
50
|
+
Measured against Unity 6000.0.40f1: **660 of 676 element coordinates identical**
|
|
51
|
+
across 40 layout cases, including a full working screen. Ten of the sixteen
|
|
50
52
|
differences are font metrics rather than layout — a browser cannot measure
|
|
51
53
|
Unity's font asset. All sixteen are named in
|
|
52
54
|
[`docs/accuracy.en.md`](docs/accuracy.en.md).
|
|
@@ -95,8 +97,9 @@ That broken feedback loop is what this library fixes.
|
|
|
95
97
|
| 3 | USS style resolver | ✅ |
|
|
96
98
|
| 4 | Yoga layout + DOM painting | ✅ |
|
|
97
99
|
| 5 | Golden tests against Unity | ✅ |
|
|
98
|
-
| 6 | **v0.1 release** |
|
|
99
|
-
| 7
|
|
100
|
+
| 6 | **v0.1 release** | ✅ |
|
|
101
|
+
| 7 | **v0.2 — ScrollView, Image, states, a measured screen** | ✅ |
|
|
102
|
+
| 8+ | Editing layer, tool integration | ⬜ |
|
|
100
103
|
|
|
101
104
|
Full plan: [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
102
105
|
|
|
@@ -207,12 +210,12 @@ Checked by comparing element geometry against the Unity Editor, case by case.
|
|
|
207
210
|
Geometry rather than pixels: Unity draws text with its own font asset and a
|
|
208
211
|
browser does not, so a pixel diff would measure the font more than the layout.
|
|
209
212
|
|
|
210
|
-
Measured against Unity on 2026-08-
|
|
213
|
+
Measured against Unity on 2026-08-12, over 40 cases and 169 elements:
|
|
211
214
|
|
|
212
215
|
| | |
|
|
213
216
|
|---|---|
|
|
214
|
-
| Cases matching exactly | **
|
|
215
|
-
| Values within 0.5px | **
|
|
217
|
+
| Cases matching exactly | **38 / 40** |
|
|
218
|
+
| Values within 0.5px | **660 / 676 (97.6%)** |
|
|
216
219
|
|
|
217
220
|
The sixteen divergences are four different things, and the distinction matters
|
|
218
221
|
more than the ratio: **ten are text metrics**, three are 1px, two are a
|
|
@@ -243,7 +246,7 @@ as nested flex, and computed values have to be resolved to fixed numbers or
|
|
|
243
246
|
calculated in C#.
|
|
244
247
|
|
|
245
248
|
**How close is the output to Unity?**
|
|
246
|
-
|
|
249
|
+
660 of 676 element coordinates identical against Unity 6000.0.40f1, across 40
|
|
247
250
|
layout cases including a complete inventory screen. Geometry is compared rather
|
|
248
251
|
than screenshots, because Unity draws text with its own font asset — which is
|
|
249
252
|
also why ten of the sixteen differences are font metrics rather than layout.
|
|
@@ -277,8 +280,8 @@ Apache-2.0
|
|
|
277
280
|
**Yoga**를 WebAssembly로 그대로 사용하기 때문에, 결과를 "비슷하게 흉내내는" 것이 아니라
|
|
278
281
|
유니티 에디터와 일치시킵니다.
|
|
279
282
|
|
|
280
|
-
Unity 6000.0.40f1과 대조한 결과, 실무형 화면을 포함한 레이아웃 케이스
|
|
281
|
-
**요소 좌표
|
|
283
|
+
Unity 6000.0.40f1과 대조한 결과, 실무형 화면을 포함한 레이아웃 케이스 40개에서
|
|
284
|
+
**요소 좌표 676개 중 660개가 일치**합니다. 어긋난 16개 중 **10개는 레이아웃이 아니라
|
|
282
285
|
폰트 메트릭** 차이입니다 — 브라우저는 유니티 폰트 에셋을 잴 수 없습니다.
|
|
283
286
|
16개 전부 [`docs/accuracy.md`](docs/accuracy.md)에 이름과 사유를 적어뒀습니다.
|
|
284
287
|
|
|
@@ -325,8 +328,9 @@ Unity 6000.0.40f1과 대조한 결과, 실무형 화면을 포함한 레이아
|
|
|
325
328
|
| 3 | USS 스타일 리졸버 | ✅ |
|
|
326
329
|
| 4 | Yoga 레이아웃 + DOM 페인팅 | ✅ |
|
|
327
330
|
| 5 | 유니티 대조 골든 테스트 | ✅ |
|
|
328
|
-
| 6 | **v0.1 공개** |
|
|
329
|
-
| 7
|
|
331
|
+
| 6 | **v0.1 공개** | ✅ |
|
|
332
|
+
| 7 | **v0.2 — ScrollView·Image·상태, 대표 화면 대조** | ✅ |
|
|
333
|
+
| 8+ | 편집 레이어, 도구 통합 | ⬜ |
|
|
330
334
|
|
|
331
335
|
전체 계획: [`docs/ROADMAP.md`](docs/ROADMAP.md)
|
|
332
336
|
|
|
@@ -410,8 +414,8 @@ pnpm build
|
|
|
410
414
|
계산이 필요한 값은 고정값으로 풀거나 C#에서 계산해야 합니다.
|
|
411
415
|
|
|
412
416
|
**유니티와 얼마나 같나요?**
|
|
413
|
-
Unity 6000.0.40f1 기준, 인벤토리 화면 전체를 포함한 레이아웃 케이스
|
|
414
|
-
**요소 좌표
|
|
417
|
+
Unity 6000.0.40f1 기준, 인벤토리 화면 전체를 포함한 레이아웃 케이스 40개에서
|
|
418
|
+
**요소 좌표 676개 중 660개 일치**입니다. 스크린샷이 아니라 좌표를 비교하는데,
|
|
415
419
|
유니티가 자기 폰트 에셋으로 글자를 그리기 때문입니다 — 그리고 그 사실 때문에
|
|
416
420
|
불일치 16개 중 **10개가 레이아웃이 아니라 폰트 메트릭**입니다. 전부
|
|
417
421
|
[`docs/accuracy.md`](docs/accuracy.md)에 이름이 적혀 있습니다.
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export { resolveStyles, explainProperty } from './style/resolve';
|
|
|
17
17
|
export type { Candidate, ComputedStyle, ComputedValue, ResolveOptions, ResolveResult, } from './style/resolve';
|
|
18
18
|
export type { Specificity } from './style/specificity';
|
|
19
19
|
export { isInherited } from './style/properties';
|
|
20
|
+
export { KNOWN_DIVERGENCES } from './known-divergences';
|
|
21
|
+
export type { KnownDivergence } from './known-divergences';
|
|
20
22
|
/**
|
|
21
23
|
* Parse UXML and USS text into a document model.
|
|
22
24
|
*
|
|
@@ -35,8 +37,23 @@ export interface ParseOptions {
|
|
|
35
37
|
* `project://database/Assets/UI/base.uss`, which only the host application
|
|
36
38
|
* knows how to turn into file contents, and there is no reason for a host to
|
|
37
39
|
* implement that lookup twice.
|
|
40
|
+
*
|
|
41
|
+
* `from` is the URL of the stylesheet containing this import — exactly the
|
|
42
|
+
* string this hook received as `url` when that containing sheet was itself
|
|
43
|
+
* resolved, unmodified. `null` for a `<Style src="…">` reference, which is
|
|
44
|
+
* not contained in any stylesheet. A relative `@import` cannot be resolved
|
|
45
|
+
* without this: `a.uss` importing `"b.uss"` means a path relative to `a.uss`,
|
|
46
|
+
* not to the UXML document, and there is no other way for the host to learn
|
|
47
|
+
* `a.uss`'s own URL at the point it resolves `b.uss`.
|
|
48
|
+
*
|
|
49
|
+
* Non-breaking: this is an added argument, not a replacement one, so an
|
|
50
|
+
* existing one-argument callback keeps working unchanged.
|
|
51
|
+
*
|
|
52
|
+
* Relative imports are deduplicated by `(url, from)`, so two parent sheets
|
|
53
|
+
* may resolve the same spelling to different files. Root-fixed URLs —
|
|
54
|
+
* `project://…` or a path beginning with `/` — are deduplicated by URL alone.
|
|
38
55
|
*/
|
|
39
|
-
resolveImport?: (url: string) => string | null;
|
|
56
|
+
resolveImport?: (url: string, from: string | null) => string | null;
|
|
40
57
|
}
|
|
41
58
|
export declare function parse(uxml: string, uss?: string, options?: ParseOptions): UxmlDocument;
|
|
42
59
|
/**
|
|
@@ -57,8 +74,15 @@ export interface RenderOptions {
|
|
|
57
74
|
* USS background images look like `url("project://database/Assets/foo.png")`
|
|
58
75
|
* or `resource("foo")`. Browsers cannot load these, so the host application
|
|
59
76
|
* must map them to real URLs. Return `null` to draw a placeholder instead.
|
|
77
|
+
*
|
|
78
|
+
* `form` says which of the two `path` came from — `'url'` is a path
|
|
79
|
+
* (project-relative or absolute), `'resource'` is a Unity Resources-folder
|
|
80
|
+
* name (no extension, resolved by Unity's own lookup rules). They are not
|
|
81
|
+
* interchangeable: a host that ignores `form` and resolves both as file
|
|
82
|
+
* paths will resolve `resource("foo")` against whatever happens to sit at
|
|
83
|
+
* that path — the wrong image, silently, if one exists there at all.
|
|
60
84
|
*/
|
|
61
|
-
resolveAsset?: (path: string) => string | null;
|
|
85
|
+
resolveAsset?: (path: string, form: 'url' | 'resource') => string | null;
|
|
62
86
|
/** Panel size in pixels. Defaults to the container's client size. */
|
|
63
87
|
size?: {
|
|
64
88
|
width: number;
|
|
@@ -94,7 +118,8 @@ export interface RenderResult {
|
|
|
94
118
|
/**
|
|
95
119
|
* Everything the renderer could not honour: unsupported controls, properties,
|
|
96
120
|
* selectors and units, plus unresolved assets. Distinct from
|
|
97
|
-
* `UxmlDocument.warnings`, which
|
|
121
|
+
* `UxmlDocument.warnings`, which covers malformed input and unresolved
|
|
122
|
+
* `<Style src="…">` / `@import` references rather than render-time support.
|
|
98
123
|
*/
|
|
99
124
|
warnings: readonly Warning[];
|
|
100
125
|
/** Painted element per model node. Lets a host map a click back to the tree. */
|