@geometra/core 0.1.0 → 0.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.
- package/dist/elements.d.ts +4 -1
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +4 -3
- package/dist/elements.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/selection.d.ts +46 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +92 -0
- package/dist/selection.js.map +1 -0
- package/dist/seo.d.ts +29 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +104 -0
- package/dist/seo.js.map +1 -0
- package/dist/tree.js +1 -1
- package/dist/tree.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/elements.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { FlexProps } from 'textura';
|
|
2
|
-
import type { StyleProps, BoxElement, TextElement, UIElement, EventHandlers } from './types.js';
|
|
2
|
+
import type { StyleProps, BoxElement, TextElement, UIElement, EventHandlers, SemanticProps } from './types.js';
|
|
3
3
|
type BoxProps = FlexProps & StyleProps & EventHandlers & {
|
|
4
4
|
key?: string;
|
|
5
|
+
semantic?: SemanticProps;
|
|
5
6
|
};
|
|
6
7
|
type TextProps = FlexProps & StyleProps & {
|
|
7
8
|
text: string;
|
|
8
9
|
font: string;
|
|
9
10
|
lineHeight: number;
|
|
10
11
|
whiteSpace?: 'normal' | 'pre-wrap';
|
|
12
|
+
selectable?: boolean;
|
|
11
13
|
key?: string;
|
|
14
|
+
semantic?: SemanticProps;
|
|
12
15
|
};
|
|
13
16
|
/** Create a box (container) element. */
|
|
14
17
|
export declare function box(props: BoxProps, children?: UIElement[]): BoxElement;
|
package/dist/elements.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE9G,KAAK,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAE,CAAA;AACnG,KAAK,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;IAClC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,aAAa,CAAA;CACzB,CAAA;AAED,wCAAwC;AACxC,wBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,GAAE,SAAS,EAAO,GAAG,UAAU,CAgB3E;AAED,kCAAkC;AAClC,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAGlD"}
|
package/dist/elements.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Create a box (container) element. */
|
|
2
2
|
export function box(props, children = []) {
|
|
3
|
-
const { onClick, onPointerDown, onPointerUp, onPointerMove, key, ...rest } = props;
|
|
3
|
+
const { onClick, onPointerDown, onPointerUp, onPointerMove, key, semantic, ...rest } = props;
|
|
4
4
|
const handlers = {};
|
|
5
5
|
if (onClick)
|
|
6
6
|
handlers.onClick = onClick;
|
|
@@ -16,11 +16,12 @@ export function box(props, children = []) {
|
|
|
16
16
|
children,
|
|
17
17
|
key,
|
|
18
18
|
handlers: Object.keys(handlers).length > 0 ? handlers : undefined,
|
|
19
|
+
semantic,
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
/** Create a text leaf element. */
|
|
22
23
|
export function text(props) {
|
|
23
|
-
const { key, ...rest } = props;
|
|
24
|
-
return { kind: 'text', props: rest, key };
|
|
24
|
+
const { key, semantic, ...rest } = props;
|
|
25
|
+
return { kind: 'text', props: rest, key, semantic };
|
|
25
26
|
}
|
|
26
27
|
//# sourceMappingURL=elements.js.map
|
package/dist/elements.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elements.js","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"elements.js","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AAcA,wCAAwC;AACxC,MAAM,UAAU,GAAG,CAAC,KAAe,EAAE,WAAwB,EAAE;IAC7D,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAA;IAC5F,MAAM,QAAQ,GAAkB,EAAE,CAAA;IAClC,IAAI,OAAO;QAAE,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAA;IACvC,IAAI,aAAa;QAAE,QAAQ,CAAC,aAAa,GAAG,aAAa,CAAA;IACzD,IAAI,WAAW;QAAE,QAAQ,CAAC,WAAW,GAAG,WAAW,CAAA;IACnD,IAAI,aAAa;QAAE,QAAQ,CAAC,aAAa,GAAG,aAAa,CAAA;IAEzD,OAAO;QACL,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,IAAI;QACX,QAAQ;QACR,GAAG;QACH,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QACjE,QAAQ;KACT,CAAA;AACH,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,IAAI,CAAC,KAAgB;IACnC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAA;IACxC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAA;AACrD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,5 +5,9 @@ export { createApp } from './app.js';
|
|
|
5
5
|
export type { App, AppOptions } from './app.js';
|
|
6
6
|
export { toLayoutTree } from './tree.js';
|
|
7
7
|
export { dispatchHit } from './hit-test.js';
|
|
8
|
-
export
|
|
8
|
+
export { collectTextNodes, getSelectedText, hitTestText } from './selection.js';
|
|
9
|
+
export type { TextNodeInfo, TextLineInfo, SelectionRange } from './selection.js';
|
|
10
|
+
export { toSemanticHTML } from './seo.js';
|
|
11
|
+
export type { SemanticHTMLOptions } from './seo.js';
|
|
12
|
+
export type { UIElement, BoxElement, TextElement, StyleProps, SemanticProps, EventHandlers, HitEvent, Component, Renderer, } from './types.js';
|
|
9
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAC9D,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGpD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAGzC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAGxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAG3C,YAAY,EACV,SAAS,EACT,UAAU,EACV,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAC9D,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGpD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAGzC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAGxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAG3C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC/E,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAGhF,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACzC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAGnD,YAAY,EACV,SAAS,EACT,UAAU,EACV,WAAW,EACX,UAAU,EACV,aAAa,EACb,aAAa,EACb,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -8,4 +8,8 @@ export { createApp } from './app.js';
|
|
|
8
8
|
export { toLayoutTree } from './tree.js';
|
|
9
9
|
// Hit testing
|
|
10
10
|
export { dispatchHit } from './hit-test.js';
|
|
11
|
+
// Text selection
|
|
12
|
+
export { collectTextNodes, getSelectedText, hitTestText } from './selection.js';
|
|
13
|
+
// SEO
|
|
14
|
+
export { toSemanticHTML } from './seo.js';
|
|
11
15
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAG9D,uBAAuB;AACvB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAEzC,YAAY;AACZ,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAGpC,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,cAAc;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAG9D,uBAAuB;AACvB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAEzC,YAAY;AACZ,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAGpC,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,cAAc;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,iBAAiB;AACjB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAG/E,MAAM;AACN,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ComputedLayout } from 'textura';
|
|
2
|
+
import type { UIElement, TextElement } from './types.js';
|
|
3
|
+
/** Info about a rendered text node's position and content. */
|
|
4
|
+
export interface TextNodeInfo {
|
|
5
|
+
element: TextElement;
|
|
6
|
+
/** Absolute x position. */
|
|
7
|
+
x: number;
|
|
8
|
+
/** Absolute y position. */
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
/** Rendered lines with character-level offsets. */
|
|
13
|
+
lines: TextLineInfo[];
|
|
14
|
+
/** Index in the flat list of all text nodes (document order). */
|
|
15
|
+
index: number;
|
|
16
|
+
}
|
|
17
|
+
export interface TextLineInfo {
|
|
18
|
+
text: string;
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
/** Cumulative x offset of each character start, relative to line x. */
|
|
22
|
+
charOffsets: number[];
|
|
23
|
+
/** Width of each character. */
|
|
24
|
+
charWidths: number[];
|
|
25
|
+
}
|
|
26
|
+
/** A selection range across text nodes. */
|
|
27
|
+
export interface SelectionRange {
|
|
28
|
+
/** Index of the anchor text node. */
|
|
29
|
+
anchorNode: number;
|
|
30
|
+
/** Character offset within the anchor node's full text. */
|
|
31
|
+
anchorOffset: number;
|
|
32
|
+
/** Index of the focus text node. */
|
|
33
|
+
focusNode: number;
|
|
34
|
+
/** Character offset within the focus node's full text. */
|
|
35
|
+
focusOffset: number;
|
|
36
|
+
}
|
|
37
|
+
/** Collect all selectable text nodes from the element tree with their absolute positions. */
|
|
38
|
+
export declare function collectTextNodes(element: UIElement, layout: ComputedLayout, offsetX: number, offsetY: number, results: TextNodeInfo[]): void;
|
|
39
|
+
/** Get the selected text from a selection range and text node info list. */
|
|
40
|
+
export declare function getSelectedText(range: SelectionRange, textNodes: TextNodeInfo[]): string;
|
|
41
|
+
/** Find which text node and character offset is at a given (x, y) point. */
|
|
42
|
+
export declare function hitTestText(textNodes: TextNodeInfo[], px: number, py: number): {
|
|
43
|
+
nodeIndex: number;
|
|
44
|
+
charOffset: number;
|
|
45
|
+
} | null;
|
|
46
|
+
//# sourceMappingURL=selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,WAAW,CAAA;IACpB,2BAA2B;IAC3B,CAAC,EAAE,MAAM,CAAA;IACT,2BAA2B;IAC3B,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,uEAAuE;IACvE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,SAAS,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,YAAY,EAAE,GACtB,IAAI,CAyBN;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,YAAY,EAAE,GACxB,MAAM,CA+BR;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CACzB,SAAS,EAAE,YAAY,EAAE,EACzB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgClD"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Collect all selectable text nodes from the element tree with their absolute positions. */
|
|
2
|
+
export function collectTextNodes(element, layout, offsetX, offsetY, results) {
|
|
3
|
+
const x = offsetX + layout.x;
|
|
4
|
+
const y = offsetY + layout.y;
|
|
5
|
+
if (element.kind === 'text') {
|
|
6
|
+
if (element.props.selectable) {
|
|
7
|
+
results.push({
|
|
8
|
+
element,
|
|
9
|
+
x,
|
|
10
|
+
y,
|
|
11
|
+
width: layout.width,
|
|
12
|
+
height: layout.height,
|
|
13
|
+
lines: [], // Populated by the renderer (needs ctx.measureText)
|
|
14
|
+
index: results.length,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < element.children.length; i++) {
|
|
20
|
+
const childLayout = layout.children[i];
|
|
21
|
+
if (childLayout) {
|
|
22
|
+
collectTextNodes(element.children[i], childLayout, x, y, results);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Get the selected text from a selection range and text node info list. */
|
|
27
|
+
export function getSelectedText(range, textNodes) {
|
|
28
|
+
if (textNodes.length === 0)
|
|
29
|
+
return '';
|
|
30
|
+
// Normalize: ensure start <= end
|
|
31
|
+
let startNode = range.anchorNode;
|
|
32
|
+
let startOffset = range.anchorOffset;
|
|
33
|
+
let endNode = range.focusNode;
|
|
34
|
+
let endOffset = range.focusOffset;
|
|
35
|
+
if (startNode > endNode || (startNode === endNode && startOffset > endOffset)) {
|
|
36
|
+
;
|
|
37
|
+
[startNode, endNode] = [endNode, startNode];
|
|
38
|
+
[startOffset, endOffset] = [endOffset, startOffset];
|
|
39
|
+
}
|
|
40
|
+
const parts = [];
|
|
41
|
+
for (let i = startNode; i <= endNode; i++) {
|
|
42
|
+
const node = textNodes[i];
|
|
43
|
+
if (!node)
|
|
44
|
+
continue;
|
|
45
|
+
const fullText = node.element.props.text;
|
|
46
|
+
if (i === startNode && i === endNode) {
|
|
47
|
+
parts.push(fullText.slice(startOffset, endOffset));
|
|
48
|
+
}
|
|
49
|
+
else if (i === startNode) {
|
|
50
|
+
parts.push(fullText.slice(startOffset));
|
|
51
|
+
}
|
|
52
|
+
else if (i === endNode) {
|
|
53
|
+
parts.push(fullText.slice(0, endOffset));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
parts.push(fullText);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return parts.join('\n');
|
|
60
|
+
}
|
|
61
|
+
/** Find which text node and character offset is at a given (x, y) point. */
|
|
62
|
+
export function hitTestText(textNodes, px, py) {
|
|
63
|
+
for (const node of textNodes) {
|
|
64
|
+
// Check if point is within the text node's bounding box
|
|
65
|
+
if (px < node.x || px > node.x + node.width || py < node.y || py > node.y + node.height) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Find the line
|
|
69
|
+
let globalCharOffset = 0;
|
|
70
|
+
for (const line of node.lines) {
|
|
71
|
+
const lineBottom = line.y + (node.element.props.lineHeight);
|
|
72
|
+
if (py >= line.y && py < lineBottom) {
|
|
73
|
+
// Find the character within the line
|
|
74
|
+
const localX = px - line.x;
|
|
75
|
+
for (let c = 0; c < line.charOffsets.length; c++) {
|
|
76
|
+
const charStart = line.charOffsets[c];
|
|
77
|
+
const charEnd = charStart + line.charWidths[c];
|
|
78
|
+
if (localX < (charStart + charEnd) / 2) {
|
|
79
|
+
return { nodeIndex: node.index, charOffset: globalCharOffset + c };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Past the end of the line — snap to end
|
|
83
|
+
return { nodeIndex: node.index, charOffset: globalCharOffset + line.text.length };
|
|
84
|
+
}
|
|
85
|
+
globalCharOffset += line.text.length;
|
|
86
|
+
}
|
|
87
|
+
// Point is within node bounds but between lines — snap to nearest
|
|
88
|
+
return { nodeIndex: node.index, charOffset: 0 };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.js","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"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"}
|
package/dist/seo.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { UIElement } from './types.js';
|
|
2
|
+
/** Options for semantic HTML generation. */
|
|
3
|
+
export interface SemanticHTMLOptions {
|
|
4
|
+
/** Page title for the <title> tag. */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** Meta description. */
|
|
7
|
+
description?: string;
|
|
8
|
+
/** Canonical URL. */
|
|
9
|
+
canonical?: string;
|
|
10
|
+
/** Open Graph metadata. */
|
|
11
|
+
og?: {
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
image?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
};
|
|
18
|
+
/** Additional <head> content (raw HTML string). */
|
|
19
|
+
headExtra?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate semantic HTML from a UIElement tree.
|
|
23
|
+
*
|
|
24
|
+
* This produces a full HTML document suitable for search engine crawlers.
|
|
25
|
+
* Serve this to user-agents like Googlebot while rendering the canvas
|
|
26
|
+
* version for real users.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toSemanticHTML(tree: UIElement, options?: SemanticHTMLOptions): string;
|
|
29
|
+
//# sourceMappingURL=seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAA2B,MAAM,YAAY,CAAA;AAEpE,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2BAA2B;IAC3B,EAAE,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;IACD,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA6DD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,EACf,OAAO,GAAE,mBAAwB,GAChC,MAAM,CA+BR"}
|
package/dist/seo.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Infer an HTML tag from a text element's font property. */
|
|
2
|
+
function inferTag(element) {
|
|
3
|
+
const font = element.props.font.toLowerCase();
|
|
4
|
+
// Detect heading-like fonts by size
|
|
5
|
+
const sizeMatch = font.match(/(\d+)px/);
|
|
6
|
+
const size = sizeMatch ? parseInt(sizeMatch[1]) : 14;
|
|
7
|
+
const isBold = font.includes('bold');
|
|
8
|
+
if (isBold && size >= 28)
|
|
9
|
+
return 'h1';
|
|
10
|
+
if (isBold && size >= 22)
|
|
11
|
+
return 'h2';
|
|
12
|
+
if (isBold && size >= 18)
|
|
13
|
+
return 'h3';
|
|
14
|
+
if (isBold && size >= 15)
|
|
15
|
+
return 'h4';
|
|
16
|
+
return 'p';
|
|
17
|
+
}
|
|
18
|
+
/** Infer an HTML tag from a box element's styling and children. */
|
|
19
|
+
function inferBoxTag(element) {
|
|
20
|
+
// Check for nav-like patterns (row of clickable items)
|
|
21
|
+
if (element.handlers?.onClick)
|
|
22
|
+
return 'button';
|
|
23
|
+
return 'div';
|
|
24
|
+
}
|
|
25
|
+
/** Escape HTML special characters. */
|
|
26
|
+
function escapeHTML(str) {
|
|
27
|
+
return str
|
|
28
|
+
.replace(/&/g, '&')
|
|
29
|
+
.replace(/</g, '<')
|
|
30
|
+
.replace(/>/g, '>')
|
|
31
|
+
.replace(/"/g, '"');
|
|
32
|
+
}
|
|
33
|
+
/** Convert a UIElement tree to a semantic HTML string body. */
|
|
34
|
+
function elementToHTML(element, indent) {
|
|
35
|
+
const pad = ' '.repeat(indent);
|
|
36
|
+
if (element.kind === 'text') {
|
|
37
|
+
const tag = element.semantic?.tag ?? inferTag(element);
|
|
38
|
+
const attrs = [];
|
|
39
|
+
if (element.semantic?.role)
|
|
40
|
+
attrs.push(`role="${escapeHTML(element.semantic.role)}"`);
|
|
41
|
+
if (element.semantic?.ariaLabel)
|
|
42
|
+
attrs.push(`aria-label="${escapeHTML(element.semantic.ariaLabel)}"`);
|
|
43
|
+
const attrStr = attrs.length ? ' ' + attrs.join(' ') : '';
|
|
44
|
+
return `${pad}<${tag}${attrStr}>${escapeHTML(element.props.text)}</${tag}>`;
|
|
45
|
+
}
|
|
46
|
+
const tag = element.semantic?.tag ?? inferBoxTag(element);
|
|
47
|
+
const attrs = [];
|
|
48
|
+
if (element.semantic?.role)
|
|
49
|
+
attrs.push(`role="${escapeHTML(element.semantic.role)}"`);
|
|
50
|
+
if (element.semantic?.ariaLabel)
|
|
51
|
+
attrs.push(`aria-label="${escapeHTML(element.semantic.ariaLabel)}"`);
|
|
52
|
+
if (element.semantic?.alt)
|
|
53
|
+
attrs.push(`aria-label="${escapeHTML(element.semantic.alt)}"`);
|
|
54
|
+
const attrStr = attrs.length ? ' ' + attrs.join(' ') : '';
|
|
55
|
+
if (element.children.length === 0) {
|
|
56
|
+
return `${pad}<${tag}${attrStr}></${tag}>`;
|
|
57
|
+
}
|
|
58
|
+
const children = element.children.map(c => elementToHTML(c, indent + 1)).join('\n');
|
|
59
|
+
return `${pad}<${tag}${attrStr}>\n${children}\n${pad}</${tag}>`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate semantic HTML from a UIElement tree.
|
|
63
|
+
*
|
|
64
|
+
* This produces a full HTML document suitable for search engine crawlers.
|
|
65
|
+
* Serve this to user-agents like Googlebot while rendering the canvas
|
|
66
|
+
* version for real users.
|
|
67
|
+
*/
|
|
68
|
+
export function toSemanticHTML(tree, options = {}) {
|
|
69
|
+
const meta = [
|
|
70
|
+
'<meta charset="UTF-8">',
|
|
71
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1.0">',
|
|
72
|
+
];
|
|
73
|
+
if (options.title)
|
|
74
|
+
meta.push(`<title>${escapeHTML(options.title)}</title>`);
|
|
75
|
+
if (options.description)
|
|
76
|
+
meta.push(`<meta name="description" content="${escapeHTML(options.description)}">`);
|
|
77
|
+
if (options.canonical)
|
|
78
|
+
meta.push(`<link rel="canonical" href="${escapeHTML(options.canonical)}">`);
|
|
79
|
+
if (options.og) {
|
|
80
|
+
if (options.og.title)
|
|
81
|
+
meta.push(`<meta property="og:title" content="${escapeHTML(options.og.title)}">`);
|
|
82
|
+
if (options.og.description)
|
|
83
|
+
meta.push(`<meta property="og:description" content="${escapeHTML(options.og.description)}">`);
|
|
84
|
+
if (options.og.image)
|
|
85
|
+
meta.push(`<meta property="og:image" content="${escapeHTML(options.og.image)}">`);
|
|
86
|
+
if (options.og.url)
|
|
87
|
+
meta.push(`<meta property="og:url" content="${escapeHTML(options.og.url)}">`);
|
|
88
|
+
if (options.og.type)
|
|
89
|
+
meta.push(`<meta property="og:type" content="${escapeHTML(options.og.type)}">`);
|
|
90
|
+
}
|
|
91
|
+
if (options.headExtra)
|
|
92
|
+
meta.push(options.headExtra);
|
|
93
|
+
const body = elementToHTML(tree, 2);
|
|
94
|
+
return `<!DOCTYPE html>
|
|
95
|
+
<html lang="en">
|
|
96
|
+
<head>
|
|
97
|
+
${meta.join('\n ')}
|
|
98
|
+
</head>
|
|
99
|
+
<body>
|
|
100
|
+
${body}
|
|
101
|
+
</body>
|
|
102
|
+
</html>`;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=seo.js.map
|
package/dist/seo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.js","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"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"}
|
package/dist/tree.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Convert a UIElement tree into a textura LayoutNode tree for layout computation. */
|
|
2
2
|
export function toLayoutTree(element) {
|
|
3
3
|
if (element.kind === 'text') {
|
|
4
|
-
const { backgroundColor: _bg, color: _c, borderColor: _bc, borderRadius: _br, opacity: _o, ...layoutProps } = element.props;
|
|
4
|
+
const { backgroundColor: _bg, color: _c, borderColor: _bc, borderRadius: _br, opacity: _o, selectable: _s, ...layoutProps } = element.props;
|
|
5
5
|
return layoutProps;
|
|
6
6
|
}
|
|
7
7
|
const { backgroundColor: _bg, color: _c, borderColor: _bc, borderRadius: _br, opacity: _o, ...layoutProps } = element.props;
|
package/dist/tree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":"AAGA,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,OAAkB;IAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":"AAGA,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,OAAkB;IAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAC3I,OAAO,WAAW,CAAA;IACpB,CAAC;IAED,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAC3H,OAAO;QACL,GAAG,WAAW;QACd,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC;KAC7C,CAAA;AACH,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,6 +7,17 @@ export interface StyleProps {
|
|
|
7
7
|
borderRadius?: number;
|
|
8
8
|
opacity?: number;
|
|
9
9
|
}
|
|
10
|
+
/** Semantic properties for SEO and accessibility. */
|
|
11
|
+
export interface SemanticProps {
|
|
12
|
+
/** HTML tag to use in semantic HTML output (e.g. 'h1', 'p', 'nav', 'article'). */
|
|
13
|
+
tag?: string;
|
|
14
|
+
/** ARIA role for accessibility (e.g. 'heading', 'navigation', 'button'). */
|
|
15
|
+
role?: string;
|
|
16
|
+
/** Alt text for images or decorative elements. */
|
|
17
|
+
alt?: string;
|
|
18
|
+
/** Aria-label for screen readers. */
|
|
19
|
+
ariaLabel?: string;
|
|
20
|
+
}
|
|
10
21
|
/** A text node in the component tree. */
|
|
11
22
|
export interface TextElement {
|
|
12
23
|
kind: 'text';
|
|
@@ -15,8 +26,12 @@ export interface TextElement {
|
|
|
15
26
|
font: string;
|
|
16
27
|
lineHeight: number;
|
|
17
28
|
whiteSpace?: 'normal' | 'pre-wrap';
|
|
29
|
+
/** Enable text selection on this element. */
|
|
30
|
+
selectable?: boolean;
|
|
18
31
|
};
|
|
19
32
|
key?: string;
|
|
33
|
+
/** Semantic hints for SEO/a11y. */
|
|
34
|
+
semantic?: SemanticProps;
|
|
20
35
|
}
|
|
21
36
|
/** A box (container) node in the component tree. */
|
|
22
37
|
export interface BoxElement {
|
|
@@ -26,6 +41,8 @@ export interface BoxElement {
|
|
|
26
41
|
key?: string;
|
|
27
42
|
/** Optional event handlers — resolved via hit-testing. */
|
|
28
43
|
handlers?: EventHandlers;
|
|
44
|
+
/** Semantic hints for SEO/a11y. */
|
|
45
|
+
semantic?: SemanticProps;
|
|
29
46
|
}
|
|
30
47
|
/** Union of all element types. */
|
|
31
48
|
export type UIElement = TextElement | BoxElement;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAExD,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG;QAC9B,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;QACZ,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAExD,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG;QAC9B,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;QACZ,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;QAClC,6CAA6C;QAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAA;IACD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,QAAQ,CAAC,EAAE,aAAa,CAAA;CACzB;AAED,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,CAAA;IACX,KAAK,EAAE,SAAS,GAAG,UAAU,CAAA;IAC7B,QAAQ,EAAE,SAAS,EAAE,CAAA;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,aAAa,CAAA;CACzB;AAED,kCAAkC;AAClC,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,CAAA;AAEhD,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAA;IAC/B,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAA;IACrC,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAA;IACnC,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAA;CACtC;AAED,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE,cAAc,CAAA;CACvB;AAED,wEAAwE;AACxE,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,CAAA;AAE1E,oDAAoD;AACpD,MAAM,WAAW,QAAQ;IACvB,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IACrD,mCAAmC;IACnC,OAAO,IAAI,IAAI,CAAA;CAChB"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geometra/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DOM-free UI framework core: components, signals, reconciler",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/razroo/
|
|
9
|
+
"url": "https://github.com/razroo/geometra",
|
|
10
10
|
"directory": "packages/core"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://razroo.github.io/
|
|
12
|
+
"homepage": "https://razroo.github.io/geometra",
|
|
13
13
|
"keywords": [
|
|
14
14
|
"ui",
|
|
15
15
|
"framework",
|