@kurot/ui 3.0.0 → 3.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/kurot/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACtG,OAAO,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/kurot/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACtG,OAAO,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
@@ -31,4 +31,5 @@ export { UILayer } from './UILayer.js';
31
31
  export { EditableText } from './EditableText.js';
32
32
  export { TextInput } from './TextInput.js';
33
33
  export { ComboBox } from './ComboBox.js';
34
+ export { BitmapLabel } from './BitmapLabel.js';
34
35
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/kurot/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA6D,MAAM,gBAAgB,CAAC;AACtG,OAAO,EAAE,KAAK,EAAoB,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAmB,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/kurot/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA6D,MAAM,gBAAgB,CAAC;AACtG,OAAO,EAAE,KAAK,EAAoB,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAoB,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAmB,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,23 @@
1
+ interface LabelTextMetrics {
2
+ text: string;
3
+ fontFamily: string;
4
+ size: number;
5
+ minimum: number;
6
+ bold: boolean;
7
+ italic: boolean;
8
+ stroke: number;
9
+ width: number;
10
+ height: number;
11
+ }
12
+ interface LabelTextFit {
13
+ size: number;
14
+ overflow: boolean;
15
+ }
16
+ /**
17
+ * Fits one plain-text line within externally constrained logical bounds.
18
+ * A finite zero bound is meaningful; NaN represents an unconstrained axis.
19
+ * Never changes authored size or text, and always measures from the base size.
20
+ */
21
+ export declare function fitLabelText(metrics: LabelTextMetrics): LabelTextFit;
22
+ export {};
23
+ //# sourceMappingURL=fit-label-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fit-label-text.d.ts","sourceRoot":"","sources":["../../../src/kurot/text/fit-label-text.ts"],"names":[],"mappings":"AAEA,UAAU,gBAAgB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf;AAED,UAAU,YAAY;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CA8BpE"}
@@ -0,0 +1,36 @@
1
+ import { measureText } from '@kurot/core';
2
+ /**
3
+ * Fits one plain-text line within externally constrained logical bounds.
4
+ * A finite zero bound is meaningful; NaN represents an unconstrained axis.
5
+ * Never changes authored size or text, and always measures from the base size.
6
+ */
7
+ export function fitLabelText(metrics) {
8
+ const fits = (size) => {
9
+ const width = measureText(metrics.text, metrics.fontFamily, size, metrics.bold, metrics.italic);
10
+ const outline = metrics.stroke * 2;
11
+ return ((!Number.isFinite(metrics.width) || width + outline <= metrics.width) &&
12
+ (!Number.isFinite(metrics.height) || size + outline <= metrics.height));
13
+ };
14
+ if (!metrics.text || fits(metrics.size)) {
15
+ return { size: metrics.size, overflow: false };
16
+ }
17
+ const minimum = Math.min(metrics.minimum, metrics.size);
18
+ if (!fits(minimum)) {
19
+ return { size: minimum, overflow: true };
20
+ }
21
+ let lower = minimum;
22
+ let upper = metrics.size;
23
+ // Bounded work even for unusual author-supplied font sizes. Keeping the
24
+ // fitting lower bound avoids rounding a result back into overflow.
25
+ for (let attempt = 0; attempt < 16 && upper - lower > 0.1; attempt++) {
26
+ const candidate = (lower + upper) / 2;
27
+ if (fits(candidate)) {
28
+ lower = candidate;
29
+ }
30
+ else {
31
+ upper = candidate;
32
+ }
33
+ }
34
+ return { size: Math.max(minimum, Math.floor(lower * 10) / 10), overflow: false };
35
+ }
36
+ //# sourceMappingURL=fit-label-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fit-label-text.js","sourceRoot":"","sources":["../../../src/kurot/text/fit-label-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAmB1C;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAAyB;IACrD,MAAM,IAAI,GAAG,CAAC,IAAY,EAAW,EAAE;QACtC,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAChG,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QACnC,OAAO,CACN,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC;YACrE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CACtE,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAChD,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,IAAI,KAAK,GAAG,OAAO,CAAC;IACpB,IAAI,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IACzB,wEAAwE;IACxE,mEAAmE;IACnE,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,EAAE,IAAI,KAAK,GAAG,KAAK,GAAG,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;QACtE,MAAM,SAAS,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACrB,KAAK,GAAG,SAAS,CAAC;QACnB,CAAC;aAAM,CAAC;YACP,KAAK,GAAG,SAAS,CAAC;QACnB,CAAC;IACF,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAClF,CAAC"}
@@ -0,0 +1,224 @@
1
+ # @kurot/ui — AI context map
2
+
3
+ Read this before exploring `src/`. It is a compressed map so an agent
4
+ unfamiliar with Kurot does not need to re-derive the architecture from scratch
5
+ each session. Treat the package source and its `src/index.ts` barrel as the
6
+ authority for current behavior and exports; this file provides the compressed
7
+ map, runtime contracts and task→file lookup.
8
+
9
+ Package identity: `@kurot/ui@3.2.0`, EUI-compatible UI framework on top of
10
+ `@kurot/core`. Peer-depends on `@kurot/core@^2.2.0`. Rewritten with standard class
11
+ inheritance and delegation — no namespace mixins, no prototype copying.
12
+ Version 3.2.0 is prepared for publication against published Core 2.2.0;
13
+ source metadata alone does not confirm UI publication.
14
+
15
+ Source root: `src/kurot/`. Public API: `src/index.ts` is a flat re-export of
16
+ 7 barrels — `core`, `layouts`, `components`, `events`, `states`, `collections`,
17
+ `binding` — see §4.
18
+
19
+ ## 1. Directory map
20
+
21
+ ```
22
+ src/kurot/
23
+ ├── core/ Layout contract + state machine + theming.
24
+ │ IUIComponent (interface every UI component implements),
25
+ │ UIState (the actual layout state machine — see §3),
26
+ │ Validator/validator (render-preparation validation scheduler),
27
+ │ Theme/getTheme()/setTheme(), IViewport, IAssetAdapter/IThemeAdapter.
28
+ ├── components/ Group, Component (delegation core — see §2), Skin, and
29
+ │ concrete widgets: Button, Label, BitmapLabel, CheckBox, RadioButton,
30
+ │ ToggleButton/ToggleSwitch, ProgressBar, HSlider/VSlider,
31
+ │ Rect, Image, EditableText, TextInput, Panel, ViewStack,
32
+ │ Scroller, UILayer, DataGroup, List, TabBar, ComboBox,
33
+ │ ItemRenderer, HScrollBar/VScrollBar, Animation, Range.
34
+ ├── text/ Internal text measurement and font-fitting algorithms.
35
+ │ Used by components; not part of the public export barrels.
36
+ ├── states/ View States: State, IOverride, SetProperty,
37
+ │ SetStateProperty, AddItems. Declarative skin state-diffing.
38
+ ├── binding/ Watcher (property-chain observer), Binding (static helpers:
39
+ │ bindProperty/bindHandler/bindProperties). Available to programmatic skins and runtime integration code.
40
+ ├── collections/ ICollection, ArrayCollection. Observable data source for
41
+ │ DataGroup/List/TabBar/ComboBox.
42
+ ├── layouts/ LayoutBase (abstract), BasicLayout, VerticalLayout,
43
+ │ HorizontalLayout (both extend LinearLayoutBase), TileLayout,
44
+ │ ILayoutTarget.
45
+ └── events/ UIEvent, ItemTapEvent, CollectionEvent, PropertyEvent,
46
+ ScrollerThrowEvent.
47
+ ```
48
+
49
+ ## 2. Non-obvious current behavior
50
+
51
+ - **`Skin` is NOT a `Group`/DisplayObject subclass.** `components/Skin.ts`
52
+ defines `class Skin extends EventDispatcher<SkinEvents>` — a plain data
53
+ holder, not a visual container. A skin's
54
+ `elementsContent: DisplayObject[]` are physically parented onto the _host_
55
+ `Component` by `Component._setSkin()`, not onto the skin itself. The skin
56
+ object itself never appears in the display tree — its only job is to
57
+ declare `skinParts`/`states`/bindings.
58
+ - `Skin.setPart(name, value)` is the intentional write-side counterpart to
59
+ `getPart(name)`: programmatic materializers use it to install authored part
60
+ names as real Skin properties. This preserves the existing EUI Skin model;
61
+ it is not an alternate Map-backed namespace. Callers must reject names that
62
+ collide with `Skin` or inherited runtime members. For Agent-generated UI,
63
+ treat such a collision as invalid authored data and surface a rename
64
+ diagnostic rather than silently rewriting or accepting the name.
65
+ - **Two independent, near-duplicate state machines exist.** `Group` implements
66
+ `states`/`currentState`/`_commitCurrentState()` itself (containers have no
67
+ skin). `Component` delegates state application to its attached
68
+ `Skin.currentState` setter. Both call the same `IOverride.apply/remove(host,
69
+ skin)` signature, but `Group._commitCurrentState()` passes `this` cast
70
+ `as unknown as Skin` — a type-unsafe workaround since Group has neither a
71
+ real Component nor a real Skin.
72
+ - `Component._invokeSkinFactory()` distinguishes KUI-compiled factory
73
+ functions (called with `.call(this)` to supply the host) from real
74
+ `class extends Skin` constructors (detected from their function source and
75
+ invoked with `new`).
76
+ - `Component.touchEnabled`/`touchChildren` diverge from the raw `Sprite`
77
+ value while `enabled === false`: the setter stores intent in
78
+ `_explicitTouchEnabled`/`_explicitTouchChildren` but forces the live value
79
+ to `false`. Reading `.touchEnabled` always returns the live (possibly
80
+ forced) value, not the cached intent — easy to miss.
81
+ - `UIState.setWidth()`/`setHeight()` write to `this._owner.$explicitWidth`, a
82
+ **core** `DisplayObject` field — not a UIState-local field. Only the
83
+ _measured/actual_ size lives in `UIState`. Setting `.width` is not a simple
84
+ property write: it triggers `invalidateProperties()` +
85
+ `invalidateDisplayList()` + parent invalidation as side effects.
86
+ - **Reading `.width`/`.height` can force a synchronous layout pass.**
87
+ `getWidth()/getHeight()` call `_validateSizeNow()` before returning — this
88
+ therefore a getter may perform layout work before returning.
89
+ - `Component.measure()` reads `UIState.getUnscaledPreferredBounds()` so skin
90
+ measurement stays in component-local units. Its own scale/rotation/skew must
91
+ affect parent layout bounds only; using transformed preferred bounds here
92
+ applies the transform twice to implicitly sized skins.
93
+ - Anchor/percent constraints (`left`/`right`/`percentWidth`, etc.) have zero
94
+ effect until the component is added to a `Group` that has a `layout`
95
+ assigned. Setting them on a standalone/unparented component is a no-op.
96
+ - `isUIComponent()` is duck-typed (`'ui' in obj`) — any object with a `.ui`
97
+ property passes, not necessarily a real `Group`/`Component`.
98
+ - **Virtual layout defaults to OFF.**
99
+ Must opt in explicitly: `list.useVirtualLayout = true`.
100
+ - `ArrayCollection.filterFunction()` **mutates `_source` in place** — filtered-
101
+ out items are permanently removed from the backing array, not just hidden
102
+ from the view. To "unfilter," reassign `source`.
103
+ Also: `source =` setter dispatches `RESET` (resets scroll position
104
+ downstream in `DataGroup`); `replaceAll()` deliberately avoids that to
105
+ preserve scroll position — called out in its own doc comment.
106
+ - `Watcher`/`Binding` are **not automatically reactive**. They depend on the
107
+ host manually dispatching `PropertyEvent.PROPERTY_CHANGE` in its setters
108
+ (`PropertyEvent.dispatchPropertyEvent(this, 'propName')`). A property whose
109
+ setter doesn't dispatch this event will never be observed by a binding.
110
+ - `Skin.$watchers` (populated by compiled skin factories) must be drained via
111
+ `Skin.unwatchAll()` — done automatically by `Component._setSkin()` when
112
+ detaching, but a hand-rolled skin creating `Watcher`s outside `$watchers`
113
+ will leak listeners.
114
+ - `UIEvent.dispatchUIEvent()` is a no-op guard: it returns early
115
+ (`if (!target.hasEventListener(eventType)) return true;`) without
116
+ constructing the event object at all if nobody's listening — used
117
+ pervasively to avoid allocation overhead on every property/position change.
118
+ - `childrenCreated()` and `UIEvent.CREATION_COMPLETE` both fire exactly **once
119
+ per component instance**, synchronously, right after `createChildren()` on
120
+ first add to stage. Re-adding a removed component does **not** re-fire
121
+ either — it instead force-validates synchronously via
122
+ `validator.validateClient()`.
123
+
124
+ ## 3. Domain-specific terminology
125
+
126
+ | Term | Definition | Where defined |
127
+ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
128
+ | `UIState` | The actual layout state machine every `Group`/`Component` delegates to via `this.ui`. Owns constraint fields (left/right/top/bottom/center/percent) packed into a `Record<K, number\|boolean>` keyed by a numeric `const enum K`. Talks back to its host only through the narrow `IUIOwner` interface, decoupling it from any specific DisplayObject subclass. | `core/UIState.ts` |
129
+ | `IUIOwner` | The callback interface `UIState` uses to talk back to its host component (`createChildren()`, `commitProperties()`, `measure()`, `updateDisplayList()`, `childrenCreated()`). Implemented by `Group` and `Component`. | `core/UIState.ts` |
130
+ | Validation cycle | Three deferred phases run in order before core renders: **validateProperties** (shallow→deep, `commitProperties()`), **validateSize** (deep→shallow, `measure()`), **validateDisplayList** (shallow→deep, `updateDisplayList()`). Scheduled through the core ticker's `callLater` queue by the global `Validator`/`validator` singleton and sorted by `$nestLevel` (a **core** `DisplayObject` field, not a UI concept — see `packages/core/src/kurot/display/DisplayObject.ts`). | `core/Validator.ts` |
131
+ | `Validator.validateClient(target)` | Forces synchronous validation of everything at or below `target`'s depth. Used by `validateNow()` and by the re-add-to-stage path in `UIState`. | `core/Validator.ts` |
132
+ | `Theme` | Maps a component's class name (`hostComponentKey`) to a default skin class name, loaded via `IThemeAdapter` (network fetch by default). Components created before the theme finishes loading queue into a `_delayList` and get skinned retroactively. | `core/Theme.ts` |
133
+ | `skinParts` | `Skin.skinParts: string[]` lists the complete named part set exposed by a skin. `Component` collects the values into one internal read-only map, exposes it through the public typed `skinParts` accessor, then invokes `onSkinReady()`. Before replacement or removal, `onSkinRemoved()` runs while the old complete map remains available. No dynamic properties are written to the component instance. | `components/Skin.ts`, `components/Component.ts` |
134
+ | Skin/State/SetProperty pattern | Declare `skinParts` + `states: State[]` in a `Skin` subclass. `Component.currentState` flows into `Skin.currentState`, which diffs old vs new `State.overrides` and calls `apply()`/`remove()` on each `IOverride`. | `states/State.ts`, `states/IOverride.ts`, `states/SetProperty.ts`, `components/Skin.ts` |
135
+ | `IViewport` | Extends `IUIComponent` with `contentWidth`/`contentHeight` (readonly) and `scrollH`/`scrollV`/`scrollEnabled`. Implemented by `Group` (and thus `DataGroup`/`List`), consumed by `Scroller`. | `core/IViewport.ts` |
136
+ | Virtual layout | Layout mode where only currently-visible-index item renderers are instantiated; off-screen renderers recycle into `DataGroup._freeRenderers` (a `Map<class, ItemRenderer[]>` free-list pool keyed by renderer class). Toggled via `layout.useVirtualLayout`. Off by default. | `layouts/LayoutBase.ts`, `components/DataGroup.ts` |
137
+ | `childrenCreated()` vs `UIEvent.CREATION_COMPLETE` | `childrenCreated()` is the imperative override hook, called synchronously once right after `createChildren()`. `CREATION_COMPLETE` is the event fired immediately after, for external listeners. Both fire at the same instant, exactly once per instance. | `core/UIState.ts` (`$onAddToStage`), `events/UIEvent.ts` |
138
+
139
+ ## 4. Public API surface (`src/index.ts`)
140
+
141
+ Flat re-export of 7 barrels, in this order: `core`, `layouts`, `components`,
142
+ `events`, `states`, `collections`, `binding`.
143
+
144
+ - **core**: `IUIComponent`, `IUIOwner`, `UIState`, `isUIComponent`, `Validator`/`validator`, `Theme`/`getTheme`/`setTheme`, `IViewport`, `IAssetAdapter`/`DefaultAssetAdapter`, `setAssetAdapter`/`getAssetAdapter`, `IThemeAdapter`/`DefaultThemeAdapter`, `Direction`, `ScrollPolicy`, `IDisplayText`, `IItemRenderer`.
145
+ - **layouts**: `ILayoutTarget`, `LayoutBase`, `BasicLayout`, `LinearLayoutBase`, `VerticalLayout`, `HorizontalLayout`, `TileLayout`, `ColumnAlign`, `RowAlign`, `JustifyAlign`, `TileOrientation`.
146
+ - **components**: `Group`/`GroupEvents`, `Component`/`ComponentEvents`, `Skin`/`SkinEvents`; `Button`, `Label`/`TextFitMode` (type), `BitmapLabel`, `CheckBox`, `RadioButton`/`RadioButtonGroup`, `ToggleButton`, `ToggleSwitch`, `ProgressBar`, `HSlider`/`VSlider`/`SliderBase`, `Rect`, `Image`, `EditableText`, `TextInput`, `Panel`, `ViewStack`, `Scroller`, `TouchScroll`, `UILayer`, `DataGroup`, `List`/`ListBase`, `TabBar`, `ComboBox`, `ItemRenderer`, `HScrollBar`/`VScrollBar`/`ScrollBarBase`, `Animation`, `Range`.
147
+ - **events**: `UIEvent`, `ItemTapEvent`, `CollectionEvent`/`CollectionEventKind`, `PropertyEvent`, `ScrollerThrowEvent`.
148
+ - **states**: `State`, `IOverride`, `SetProperty`, `SetStateProperty`, `AddItems`.
149
+ - **collections**: `ICollection`, `ArrayCollection`.
150
+ - **binding**: `Watcher`, `Binding`.
151
+
152
+ ## 5. Dependency on `@kurot/core` — and the dirty-flag interaction
153
+
154
+ Builds directly on core's `DisplayObject`/`DisplayObjectContainer`/`Sprite`
155
+ (`Group` and `Component` both `extend Sprite`), `EventDispatcher`/`Event`,
156
+ `Rectangle`/`Matrix`/`Point`, and `TextField` (wrapped by `Label`/
157
+ `EditableText`).
158
+
159
+ **UIState deliberately hooks into core's render-dirty system.** In
160
+ `UIState._setActualSize()`, after updating width/height it explicitly calls
161
+ `this._owner.$markDirty()` — the source comment explains why: a layout size
162
+ change affects rendered bounds, and since UI layout is deferred to the core
163
+ ticker's render-preparation queue (while core's dirty propagation runs
164
+ synchronously on the mutation's own tick), a `cacheAsBitmap`-flagged ancestor could otherwise
165
+ render a stale bitmap after deferred measurement completes. Similarly,
166
+ `Skin`'s re-add-to-stage path forces `validateNow()` synchronously to avoid
167
+ `structureDirty` firing before the `Validator` has filled in graphics
168
+ commands. **Takeaway: UI validation is deferred but runs through core's ticker
169
+ before the corresponding render; add-to-stage and size changes still explicitly
170
+ bridge layout invalidation into core's render-dirty system.**
171
+
172
+ Also: `Group`/`Component` override several `$`-prefixed **internal** core
173
+ methods (`$updateUseTransform`, `$setMatrix`, `$setAnchorOffsetX/Y`,
174
+ `$setX/Y`) purely to hook `ui.$invalidateParentLayout()`. Code that mutates
175
+ transform directly at the core level bypassing these overrides risks not
176
+ triggering a parent layout re-validation — a coupling point worth knowing
177
+ before patching core's transform code.
178
+
179
+ ## 6. Task → file map
180
+
181
+ | I want to... | Look at |
182
+ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
183
+ | Add a new skinnable widget | `components/Component.ts` for the base pattern, model on `components/Button.ts` |
184
+ | Add a new container-only widget (no skin) | `components/Group.ts` |
185
+ | Change Label font fitting | `text/fit-label-text.ts` for metric calculation; `components/Label.ts` for layout and lifecycle integration |
186
+ | Add a new layout algorithm | `layouts/LayoutBase.ts`, follow `layouts/VerticalLayout.ts` |
187
+ | Debug a state not applying | `components/Skin.ts` (`currentState` setter, `_applyState`), `states/State.ts`, `states/IOverride.ts` |
188
+ | Debug a binding not firing | Check the source property's setter actually calls `PropertyEvent.dispatchPropertyEvent` — see `binding/Watcher.ts` |
189
+ | Add virtual-layout support to a new layout | `layouts/LayoutBase.ts` (`elementAdded`/`elementRemoved`/`getElementIndicesInView`/`clearVirtualLayoutCache`), reference `layouts/VerticalLayout.ts`'s `elementSizeTable` |
190
+ | Understand validation/render scheduling order | `core/Validator.ts`, core `player/SystemTicker.ts` |
191
+ | Measure UI validation/rendering performance | `examples/benchmark/` for the visual runner, scenarios, Playwright automation, and local JSON/Markdown reports |
192
+
193
+ ## Label text layout contract in 3.1.0
194
+
195
+ Label defaults to multiline; explicit false draws only the first hard-separated
196
+ line without wrapping. EditableText defaults to single-line and rejects
197
+ shrinking. `wordWrap` chooses Unicode versus character boundaries only while
198
+ multiline is enabled.
199
+
200
+ `TextFitMode` is exported with Label. `textFit` is `none` by default or `shrink`
201
+ for single-line Labels. `minFontSize` defaults to 12 and must be finite and >= 1.
202
+ `size` stays authored; `renderedSize` and `textFitOverflow` are read-only results
203
+ after validation. Fitting uses fixed axes, parent layout constraints or explicit
204
+ maximums, reserves outline thickness and caches unchanged metrics. Measured
205
+ automatic bounds are not fed back as fit constraints.
206
+
207
+ `label.invalidateSize()` also invalidates Core line metrics. Call it after
208
+ late font loading, even if the family name did not change. This requires
209
+ Core >= 2.1.0 for that API; UI 3.2.0's package requirement is Core ^2.2.0.
210
+ There is no global font-readiness listener.
211
+
212
+ See [Label text layout](label-text-layout.md) for fit bounds, state restoration,
213
+ font readiness and shared-schema boundaries. Native UI still depends only on
214
+ Core; KUI authoring uses ui-document 0.11.x, published CLI 3.3.0 and
215
+ published ui-runtime 0.8.2. Label presets expand in consumer-owned
216
+ copies before native rendering; UI does not read style.json or consume textStyle.
217
+
218
+ ## Bitmap labels in 3.2.0
219
+
220
+ [bitmap-label.md](bitmap-label.md) documents the new exported BitmapLabel,
221
+ using one BitmapText child and the configured Core resource singleton for string
222
+ font names. Stale async results are guarded by request generation. Core ^2.2.0
223
+ is required; local Core overrides have been removed. Published UI 3.1.0 does
224
+ not contain it. KUI catalog, installed compilers and Editor adoption are separate.