@shipload/item-renderer 0.2.2 → 0.2.3

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.
Files changed (67) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/bun.lock +2 -2
  3. package/package.json +2 -2
  4. package/scripts/check-bundle-size.ts +21 -21
  5. package/scripts/copy-fonts.ts +19 -19
  6. package/scripts/preview.ts +13 -15
  7. package/src/assets/stardust-base64.ts +1 -1
  8. package/src/errors.ts +8 -8
  9. package/src/fonts/index.ts +25 -26
  10. package/src/fonts/load-bun.ts +9 -9
  11. package/src/index.ts +21 -21
  12. package/src/links.ts +11 -11
  13. package/src/meta.ts +16 -16
  14. package/src/payload/base64url.ts +16 -16
  15. package/src/payload/codec.ts +13 -13
  16. package/src/primitives/category-icon.ts +69 -48
  17. package/src/primitives/compact-row.ts +13 -13
  18. package/src/primitives/divider.ts +9 -9
  19. package/src/primitives/icon-hex.ts +18 -16
  20. package/src/primitives/module-slot.ts +73 -73
  21. package/src/primitives/panel.ts +10 -10
  22. package/src/primitives/quantity-badge.ts +13 -13
  23. package/src/primitives/span-paragraph.ts +48 -50
  24. package/src/primitives/stat-bar.ts +24 -24
  25. package/src/primitives/svg.ts +13 -13
  26. package/src/primitives/text.ts +25 -25
  27. package/src/primitives/wrap.ts +12 -12
  28. package/src/render.ts +15 -19
  29. package/src/templates/_shared.ts +5 -6
  30. package/src/templates/component.ts +67 -65
  31. package/src/templates/index.ts +17 -17
  32. package/src/templates/item-cell.ts +48 -45
  33. package/src/templates/module.ts +83 -81
  34. package/src/templates/packed-entity.ts +12 -14
  35. package/src/templates/resource.ts +63 -65
  36. package/src/templates/ship-panel.ts +66 -71
  37. package/src/templates/social-card.ts +27 -25
  38. package/src/tokens/colors.ts +29 -29
  39. package/src/tokens/index.ts +6 -6
  40. package/src/tokens/spacing.ts +1 -1
  41. package/src/tokens/typography.ts +1 -1
  42. package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
  43. package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
  44. package/test/base64url.test.ts +22 -22
  45. package/test/codec.test.ts +26 -35
  46. package/test/errors.test.ts +21 -21
  47. package/test/fixtures/cargo-items.ts +43 -43
  48. package/test/fonts.test.ts +23 -23
  49. package/test/links-meta.test.ts +37 -37
  50. package/test/pixel.test.ts +44 -41
  51. package/test/primitives-category-icon.test.ts +74 -67
  52. package/test/primitives-compact-row.test.ts +29 -29
  53. package/test/primitives-domain.test.ts +61 -50
  54. package/test/primitives-layout.test.ts +47 -47
  55. package/test/primitives-module-slot.test.ts +58 -58
  56. package/test/render.test.ts +38 -35
  57. package/test/sanity.test.ts +5 -5
  58. package/test/sdk-link.test.ts +13 -13
  59. package/test/svg.test.ts +24 -22
  60. package/test/templates-component.test.ts +32 -32
  61. package/test/templates-dispatch.test.ts +29 -29
  62. package/test/templates-item-cell.test.ts +79 -79
  63. package/test/templates-module.test.ts +52 -52
  64. package/test/templates-packed-entity.test.ts +42 -42
  65. package/test/templates-resource.test.ts +61 -61
  66. package/test/templates-ship-panel.test.ts +65 -61
  67. package/test/tokens.test.ts +28 -26
@@ -1,26 +1,26 @@
1
- import { Serializer } from '@wharfkit/antelope'
2
- import { ServerContract } from '@shipload/sdk'
3
- import { InvalidPayloadError } from '../errors.ts'
4
- import { base64UrlToBytes, bytesToBase64Url } from './base64url.ts'
1
+ import { Serializer } from "@wharfkit/antelope";
2
+ import { ServerContract } from "@shipload/sdk";
3
+ import { InvalidPayloadError } from "../errors.ts";
4
+ import { base64UrlToBytes, bytesToBase64Url } from "./base64url.ts";
5
5
 
6
- export type CargoItem = InstanceType<typeof ServerContract.Types.cargo_item>
7
- export type CargoItemLike = Parameters<typeof ServerContract.Types.cargo_item.from>[0]
6
+ export type CargoItem = InstanceType<typeof ServerContract.Types.cargo_item>;
7
+ export type CargoItemLike = Parameters<typeof ServerContract.Types.cargo_item.from>[0];
8
8
 
9
9
  export function encodePayload(input: CargoItemLike): string {
10
- const item = ServerContract.Types.cargo_item.from(input)
11
- const bytes = Serializer.encode({ object: item }).array
12
- return bytesToBase64Url(bytes)
10
+ const item = ServerContract.Types.cargo_item.from(input);
11
+ const bytes = Serializer.encode({ object: item }).array;
12
+ return bytesToBase64Url(bytes);
13
13
  }
14
14
 
15
15
  export function decodePayload(input: string): CargoItem {
16
- if (input.length === 0) throw new InvalidPayloadError('empty payload')
17
- const bytes = base64UrlToBytes(input)
16
+ if (input.length === 0) throw new InvalidPayloadError("empty payload");
17
+ const bytes = base64UrlToBytes(input);
18
18
  try {
19
19
  return Serializer.decode({
20
20
  data: bytes,
21
21
  type: ServerContract.Types.cargo_item,
22
- }) as CargoItem
22
+ }) as CargoItem;
23
23
  } catch (e) {
24
- throw new InvalidPayloadError(`cargo_item decode failed: ${(e as Error).message}`)
24
+ throw new InvalidPayloadError(`cargo_item decode failed: ${(e as Error).message}`);
25
25
  }
26
26
  }
@@ -1,28 +1,28 @@
1
- import type { CategoryIconShape } from '@shipload/sdk'
2
- import { el } from './svg.ts'
1
+ import type { CategoryIconShape } from "@shipload/sdk";
2
+ import { el } from "./svg.ts";
3
3
 
4
4
  export interface CategoryIconPathOpts {
5
- shape: CategoryIconShape
6
- cx: number
7
- cy: number
8
- size: number
9
- color: string
10
- strokeWidth?: number
5
+ shape: CategoryIconShape;
6
+ cx: number;
7
+ cy: number;
8
+ size: number;
9
+ color: string;
10
+ strokeWidth?: number;
11
11
  }
12
12
 
13
13
  export interface CategoryIconSvgOpts {
14
- size?: number
15
- color?: string
16
- strokeWidth?: number
14
+ size?: number;
15
+ color?: string;
16
+ strokeWidth?: number;
17
17
  }
18
18
 
19
19
  function hexPoints(cx: number, cy: number, r: number): string {
20
- const pts: string[] = []
20
+ const pts: string[] = [];
21
21
  for (let i = 0; i < 6; i++) {
22
- const angle = (Math.PI / 3) * i - Math.PI / 2
23
- pts.push(`${(cx + r * Math.cos(angle)).toFixed(2)},${(cy + r * Math.sin(angle)).toFixed(2)}`)
22
+ const angle = (Math.PI / 3) * i - Math.PI / 2;
23
+ pts.push(`${(cx + r * Math.cos(angle)).toFixed(2)},${(cy + r * Math.sin(angle)).toFixed(2)}`);
24
24
  }
25
- return pts.join(' ')
25
+ return pts.join(" ");
26
26
  }
27
27
 
28
28
  function diamondPoints(cx: number, cy: number, r: number): string {
@@ -31,57 +31,78 @@ function diamondPoints(cx: number, cy: number, r: number): string {
31
31
  `${(cx + r).toFixed(2)},${cy.toFixed(2)}`,
32
32
  `${cx.toFixed(2)},${(cy + r).toFixed(2)}`,
33
33
  `${(cx - r).toFixed(2)},${cy.toFixed(2)}`,
34
- ].join(' ')
34
+ ].join(" ");
35
35
  }
36
36
 
37
37
  function starPoints(cx: number, cy: number, r: number): string {
38
- const inner = r * 0.45
39
- const pts: string[] = []
38
+ const inner = r * 0.45;
39
+ const pts: string[] = [];
40
40
  for (let i = 0; i < 10; i++) {
41
- const angle = (Math.PI / 5) * i - Math.PI / 2
42
- const radius = i % 2 === 0 ? r : inner
43
- pts.push(`${(cx + radius * Math.cos(angle)).toFixed(2)},${(cy + radius * Math.sin(angle)).toFixed(2)}`)
41
+ const angle = (Math.PI / 5) * i - Math.PI / 2;
42
+ const radius = i % 2 === 0 ? r : inner;
43
+ pts.push(
44
+ `${(cx + radius * Math.cos(angle)).toFixed(2)},${(cy + radius * Math.sin(angle)).toFixed(2)}`,
45
+ );
44
46
  }
45
- return pts.join(' ')
47
+ return pts.join(" ");
46
48
  }
47
49
 
48
- export function categoryIconPath({ shape, cx, cy, size, color, strokeWidth }: CategoryIconPathOpts): string {
49
- const r = size / 2
50
- const stroked = strokeWidth && strokeWidth > 0
50
+ export function categoryIconPath({
51
+ shape,
52
+ cx,
53
+ cy,
54
+ size,
55
+ color,
56
+ strokeWidth,
57
+ }: CategoryIconPathOpts): string {
58
+ const r = size / 2;
59
+ const stroked = strokeWidth && strokeWidth > 0;
51
60
  const shapeAttrs = stroked
52
- ? { fill: 'none', stroke: color, 'stroke-width': strokeWidth, 'stroke-linejoin': 'round' as const }
53
- : { fill: color }
61
+ ? {
62
+ fill: "none",
63
+ stroke: color,
64
+ "stroke-width": strokeWidth,
65
+ "stroke-linejoin": "round" as const,
66
+ }
67
+ : { fill: color };
54
68
  switch (shape) {
55
- case 'hex':
56
- return el('polygon', { points: hexPoints(cx, cy, r), ...shapeAttrs })
57
- case 'diamond':
58
- return el('polygon', { points: diamondPoints(cx, cy, r), ...shapeAttrs })
59
- case 'star':
60
- return el('polygon', { points: starPoints(cx, cy, r), ...shapeAttrs })
61
- case 'circle':
62
- return el('circle', { cx, cy, r, ...shapeAttrs })
63
- case 'square':
64
- return el('rect', { x: cx - r, y: cy - r, width: size, height: size, ...shapeAttrs })
69
+ case "hex":
70
+ return el("polygon", { points: hexPoints(cx, cy, r), ...shapeAttrs });
71
+ case "diamond":
72
+ return el("polygon", { points: diamondPoints(cx, cy, r), ...shapeAttrs });
73
+ case "star":
74
+ return el("polygon", { points: starPoints(cx, cy, r), ...shapeAttrs });
75
+ case "circle":
76
+ return el("circle", { cx, cy, r, ...shapeAttrs });
77
+ case "square":
78
+ return el("rect", { x: cx - r, y: cy - r, width: size, height: size, ...shapeAttrs });
65
79
  }
66
- const _exhaustive: never = shape
67
- throw new Error(`Unknown CategoryIconShape: ${String(_exhaustive)}`)
80
+ const _exhaustive: never = shape;
81
+ throw new Error(`Unknown CategoryIconShape: ${String(_exhaustive)}`);
68
82
  }
69
83
 
70
84
  export function categoryIconSvg(shape: CategoryIconShape, opts: CategoryIconSvgOpts = {}): string {
71
- const size = opts.size ?? 16
72
- const color = opts.color ?? '#ffffff'
73
- const cx = size / 2
74
- const cy = size / 2
75
- const iconSize = size * 0.85
76
- const inner = categoryIconPath({ shape, cx, cy, size: iconSize, color, strokeWidth: opts.strokeWidth })
85
+ const size = opts.size ?? 16;
86
+ const color = opts.color ?? "#ffffff";
87
+ const cx = size / 2;
88
+ const cy = size / 2;
89
+ const iconSize = size * 0.85;
90
+ const inner = categoryIconPath({
91
+ shape,
92
+ cx,
93
+ cy,
94
+ size: iconSize,
95
+ color,
96
+ strokeWidth: opts.strokeWidth,
97
+ });
77
98
  return el(
78
- 'svg',
99
+ "svg",
79
100
  {
80
- xmlns: 'http://www.w3.org/2000/svg',
101
+ xmlns: "http://www.w3.org/2000/svg",
81
102
  width: size,
82
103
  height: size,
83
104
  viewBox: `0 0 ${size} ${size}`,
84
105
  },
85
106
  inner,
86
- )
107
+ );
87
108
  }
@@ -1,19 +1,19 @@
1
- import { text } from './text.ts'
2
- import { tokens } from '../tokens/index.ts'
1
+ import { text } from "./text.ts";
2
+ import { tokens } from "../tokens/index.ts";
3
3
 
4
4
  export interface CompactRowProps {
5
- x: number
6
- y: number
7
- width: number
8
- label: string
9
- value: string
10
- labelColor?: string
11
- valueColor?: string
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ label: string;
9
+ value: string;
10
+ labelColor?: string;
11
+ valueColor?: string;
12
12
  }
13
13
 
14
14
  export function compactRow(p: CompactRowProps): string {
15
- const labelColor = p.labelColor ?? tokens.colors.text.secondary
16
- const valueColor = p.valueColor ?? tokens.colors.text.primary
15
+ const labelColor = p.labelColor ?? tokens.colors.text.secondary;
16
+ const valueColor = p.valueColor ?? tokens.colors.text.primary;
17
17
  return (
18
18
  text({
19
19
  x: p.x,
@@ -32,7 +32,7 @@ export function compactRow(p: CompactRowProps): string {
32
32
  weight: 700,
33
33
  family: tokens.typography.sans,
34
34
  color: valueColor,
35
- anchor: 'end',
35
+ anchor: "end",
36
36
  })
37
- )
37
+ );
38
38
  }
@@ -1,20 +1,20 @@
1
- import { el } from './svg.ts'
2
- import { tokens } from '../tokens/index.ts'
1
+ import { el } from "./svg.ts";
2
+ import { tokens } from "../tokens/index.ts";
3
3
 
4
4
  export interface DividerProps {
5
- x: number
6
- y: number
7
- width: number
8
- color?: string
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ color?: string;
9
9
  }
10
10
 
11
11
  export function divider(props: DividerProps): string {
12
- return el('line', {
12
+ return el("line", {
13
13
  x1: props.x,
14
14
  x2: props.x + props.width,
15
15
  y1: props.y,
16
16
  y2: props.y,
17
17
  stroke: props.color ?? tokens.colors.surface.panelBorder,
18
- 'stroke-width': 1,
19
- })
18
+ "stroke-width": 1,
19
+ });
20
20
  }
@@ -1,20 +1,20 @@
1
- import { el } from './svg.ts'
2
- import { text } from './text.ts'
3
- import { tokens } from '../tokens/index.ts'
1
+ import { el } from "./svg.ts";
2
+ import { text } from "./text.ts";
3
+ import { tokens } from "../tokens/index.ts";
4
4
 
5
5
  export interface IconHexProps {
6
- x: number
7
- y: number
8
- color: string
9
- code: string
6
+ x: number;
7
+ y: number;
8
+ color: string;
9
+ code: string;
10
10
  }
11
11
 
12
12
  export function iconHex({ x, y, color, code }: IconHexProps): string {
13
- const size = tokens.spacing.iconHexSize
14
- const h = size
15
- const w = size * 1.1547 // flat-top hex aspect
16
- const cx = x + w / 2
17
- const cy = y + h / 2
13
+ const size = tokens.spacing.iconHexSize;
14
+ const h = size;
15
+ const w = size * 1.1547; // flat-top hex aspect
16
+ const cx = x + w / 2;
17
+ const cy = y + h / 2;
18
18
  const points = [
19
19
  [cx - w / 2, cy],
20
20
  [cx - w / 4, cy - h / 2],
@@ -22,9 +22,11 @@ export function iconHex({ x, y, color, code }: IconHexProps): string {
22
22
  [cx + w / 2, cy],
23
23
  [cx + w / 4, cy + h / 2],
24
24
  [cx - w / 4, cy + h / 2],
25
- ].map(([px, py]) => `${px?.toFixed(1)},${py?.toFixed(1)}`).join(' ')
25
+ ]
26
+ .map(([px, py]) => `${px?.toFixed(1)},${py?.toFixed(1)}`)
27
+ .join(" ");
26
28
  return (
27
- el('polygon', { points, fill: 'none', stroke: color, 'stroke-width': 1.5 }) +
29
+ el("polygon", { points, fill: "none", stroke: color, "stroke-width": 1.5 }) +
28
30
  text({
29
31
  x: cx,
30
32
  y: cy + 3,
@@ -33,7 +35,7 @@ export function iconHex({ x, y, color, code }: IconHexProps): string {
33
35
  weight: 700,
34
36
  family: tokens.typography.mono,
35
37
  color,
36
- anchor: 'middle',
38
+ anchor: "middle",
37
39
  })
38
- )
40
+ );
39
41
  }
@@ -1,62 +1,62 @@
1
- import { el } from './svg.ts'
2
- import { text } from './text.ts'
3
- import { wrapText } from './wrap.ts'
4
- import { tokens } from '../tokens/index.ts'
5
- import type { TextSpan } from '@shipload/sdk'
1
+ import { el } from "./svg.ts";
2
+ import { text } from "./text.ts";
3
+ import { wrapText } from "./wrap.ts";
4
+ import { tokens } from "../tokens/index.ts";
5
+ import type { TextSpan } from "@shipload/sdk";
6
6
 
7
7
  export interface ModuleSlotProps {
8
- x: number
9
- y: number
10
- width: number
11
- installed: boolean
12
- capability?: string
13
- description?: string | TextSpan[]
14
- accentColor?: string
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ installed: boolean;
12
+ capability?: string;
13
+ description?: string | TextSpan[];
14
+ accentColor?: string;
15
15
  }
16
16
 
17
17
  const EMPTY_DIAMOND = (cx: number, cy: number, color: string) =>
18
- el('polygon', {
18
+ el("polygon", {
19
19
  points: `${cx},${cy - 5} ${cx + 5},${cy} ${cx},${cy + 5} ${cx - 5},${cy}`,
20
- fill: 'none',
20
+ fill: "none",
21
21
  stroke: color,
22
- 'stroke-width': 1,
23
- })
22
+ "stroke-width": 1,
23
+ });
24
24
 
25
25
  const FILLED_DIAMOND = (cx: number, cy: number, color: string) =>
26
- el('polygon', {
26
+ el("polygon", {
27
27
  points: `${cx},${cy - 5} ${cx + 5},${cy} ${cx},${cy + 5} ${cx - 5},${cy}`,
28
28
  fill: color,
29
- })
29
+ });
30
30
 
31
31
  function escapeXml(s: string): string {
32
32
  return s
33
- .replace(/&/g, '&amp;')
34
- .replace(/</g, '&lt;')
35
- .replace(/>/g, '&gt;')
36
- .replace(/"/g, '&quot;')
33
+ .replace(/&/g, "&amp;")
34
+ .replace(/</g, "&lt;")
35
+ .replace(/>/g, "&gt;")
36
+ .replace(/"/g, "&quot;");
37
37
  }
38
38
 
39
39
  function sliceSpans(spans: TextSpan[], start: number, end: number): TextSpan[] {
40
- const out: TextSpan[] = []
41
- let cursor = 0
40
+ const out: TextSpan[] = [];
41
+ let cursor = 0;
42
42
  for (const span of spans) {
43
- const spanStart = cursor
44
- const spanEnd = cursor + span.text.length
45
- cursor = spanEnd
46
- if (spanEnd <= start || spanStart >= end) continue
47
- const sliceStart = Math.max(0, start - spanStart)
48
- const sliceEnd = span.text.length - Math.max(0, spanEnd - end)
49
- const txt = span.text.slice(sliceStart, sliceEnd)
50
- if (txt.length === 0) continue
51
- out.push(span.highlight ? { text: txt, highlight: true } : { text: txt })
43
+ const spanStart = cursor;
44
+ const spanEnd = cursor + span.text.length;
45
+ cursor = spanEnd;
46
+ if (spanEnd <= start || spanStart >= end) continue;
47
+ const sliceStart = Math.max(0, start - spanStart);
48
+ const sliceEnd = span.text.length - Math.max(0, spanEnd - end);
49
+ const txt = span.text.slice(sliceStart, sliceEnd);
50
+ if (txt.length === 0) continue;
51
+ out.push(span.highlight ? { text: txt, highlight: true } : { text: txt });
52
52
  }
53
- return out
53
+ return out;
54
54
  }
55
55
 
56
56
  export function moduleSlot(props: ModuleSlotProps): string {
57
- const iconX = props.x + 6
58
- const iconY = props.y + 6
59
- const textX = props.x + 20
57
+ const iconX = props.x + 6;
58
+ const iconY = props.y + 6;
59
+ const textX = props.x + 20;
60
60
 
61
61
  if (!props.installed) {
62
62
  return (
@@ -64,21 +64,21 @@ export function moduleSlot(props: ModuleSlotProps): string {
64
64
  text({
65
65
  x: textX,
66
66
  y: iconY + 3,
67
- value: 'Empty module',
67
+ value: "Empty module",
68
68
  size: tokens.typography.sizes.body,
69
69
  color: tokens.colors.text.muted,
70
70
  })
71
- )
71
+ );
72
72
  }
73
73
 
74
- const accent = props.accentColor ?? tokens.colors.text.accent
75
- const label = `${props.capability ?? 'Module'}: `
74
+ const accent = props.accentColor ?? tokens.colors.text.accent;
75
+ const label = `${props.capability ?? "Module"}: `;
76
76
 
77
- const desc = props.description
77
+ const desc = props.description;
78
78
  const isEmpty =
79
79
  !desc ||
80
- (typeof desc === 'string' && desc.length === 0) ||
81
- (Array.isArray(desc) && desc.length === 0)
80
+ (typeof desc === "string" && desc.length === 0) ||
81
+ (Array.isArray(desc) && desc.length === 0);
82
82
 
83
83
  if (isEmpty) {
84
84
  return (
@@ -91,57 +91,57 @@ export function moduleSlot(props: ModuleSlotProps): string {
91
91
  weight: 600,
92
92
  color: tokens.colors.text.primary,
93
93
  })
94
- )
94
+ );
95
95
  }
96
96
 
97
- const descSpans: TextSpan[] = typeof desc === 'string' ? [{ text: desc }] : desc
98
- const descPlain = descSpans.map((s) => s.text).join('')
99
- const combined = label + descPlain
100
- const lines = wrapText({ value: combined, charsPerLine: 36 })
97
+ const descSpans: TextSpan[] = typeof desc === "string" ? [{ text: desc }] : desc;
98
+ const descPlain = descSpans.map((s) => s.text).join("");
99
+ const combined = label + descPlain;
100
+ const lines = wrapText({ value: combined, charsPerLine: 36 });
101
101
 
102
- const highlightColor = tokens.colors.text.accent
103
- const bodyColor = tokens.colors.text.secondary
104
- const labelColor = tokens.colors.text.primary
105
- const size = tokens.typography.sizes.body
106
- const fontFamily = escapeXml(tokens.typography.sans)
107
- const labelEnd = label.length
102
+ const highlightColor = tokens.colors.text.accent;
103
+ const bodyColor = tokens.colors.text.secondary;
104
+ const labelColor = tokens.colors.text.primary;
105
+ const size = tokens.typography.sizes.body;
106
+ const fontFamily = escapeXml(tokens.typography.sans);
107
+ const labelEnd = label.length;
108
108
 
109
- let offset = 0
109
+ let offset = 0;
110
110
  const textBlocks = lines
111
111
  .map((line, i) => {
112
- const lineStart = combined.indexOf(line, offset)
113
- const lineEnd = lineStart + line.length
114
- offset = lineEnd
115
- const y = iconY + 3 + i * 14
112
+ const lineStart = combined.indexOf(line, offset);
113
+ const lineEnd = lineStart + line.length;
114
+ offset = lineEnd;
115
+ const y = iconY + 3 + i * 14;
116
116
 
117
- const tspans: string[] = []
117
+ const tspans: string[] = [];
118
118
 
119
119
  if (lineStart < labelEnd) {
120
- const labelSliceEnd = Math.min(lineEnd, labelEnd)
121
- const labelText = combined.slice(lineStart, labelSliceEnd)
120
+ const labelSliceEnd = Math.min(lineEnd, labelEnd);
121
+ const labelText = combined.slice(lineStart, labelSliceEnd);
122
122
  if (labelText.length > 0) {
123
123
  tspans.push(
124
124
  `<tspan font-weight="600" fill="${labelColor}">${escapeXml(labelText)}</tspan>`,
125
- )
125
+ );
126
126
  }
127
127
  if (lineEnd > labelEnd) {
128
- const descSlice = sliceSpans(descSpans, 0, lineEnd - labelEnd)
128
+ const descSlice = sliceSpans(descSpans, 0, lineEnd - labelEnd);
129
129
  for (const s of descSlice) {
130
- const fill = s.highlight ? highlightColor : bodyColor
131
- tspans.push(`<tspan fill="${fill}">${escapeXml(s.text)}</tspan>`)
130
+ const fill = s.highlight ? highlightColor : bodyColor;
131
+ tspans.push(`<tspan fill="${fill}">${escapeXml(s.text)}</tspan>`);
132
132
  }
133
133
  }
134
134
  } else {
135
- const descSlice = sliceSpans(descSpans, lineStart - labelEnd, lineEnd - labelEnd)
135
+ const descSlice = sliceSpans(descSpans, lineStart - labelEnd, lineEnd - labelEnd);
136
136
  for (const s of descSlice) {
137
- const fill = s.highlight ? highlightColor : bodyColor
138
- tspans.push(`<tspan fill="${fill}">${escapeXml(s.text)}</tspan>`)
137
+ const fill = s.highlight ? highlightColor : bodyColor;
138
+ tspans.push(`<tspan fill="${fill}">${escapeXml(s.text)}</tspan>`);
139
139
  }
140
140
  }
141
141
 
142
- return `<text x="${textX}" y="${y}" font-family="${fontFamily}" font-size="${size}">${tspans.join('')}</text>`
142
+ return `<text x="${textX}" y="${y}" font-family="${fontFamily}" font-size="${size}">${tspans.join("")}</text>`;
143
143
  })
144
- .join('')
144
+ .join("");
145
145
 
146
- return FILLED_DIAMOND(iconX, iconY, accent) + textBlocks
146
+ return FILLED_DIAMOND(iconX, iconY, accent) + textBlocks;
147
147
  }
@@ -1,16 +1,16 @@
1
- import { el } from './svg.ts'
2
- import { tokens } from '../tokens/index.ts'
1
+ import { el } from "./svg.ts";
2
+ import { tokens } from "../tokens/index.ts";
3
3
 
4
4
  export interface PanelProps {
5
- width: number
6
- height: number
7
- borderColor?: string
5
+ width: number;
6
+ height: number;
7
+ borderColor?: string;
8
8
  }
9
9
 
10
10
  export function panel(props: PanelProps): string {
11
- const { width, height, borderColor } = props
12
- const r = tokens.spacing.cornerRadius
13
- return el('rect', {
11
+ const { width, height, borderColor } = props;
12
+ const r = tokens.spacing.cornerRadius;
13
+ return el("rect", {
14
14
  x: 0.5,
15
15
  y: 0.5,
16
16
  width: width - 1,
@@ -19,6 +19,6 @@ export function panel(props: PanelProps): string {
19
19
  ry: r,
20
20
  fill: tokens.colors.surface.panel,
21
21
  stroke: borderColor ?? tokens.colors.surface.panelBorder,
22
- 'stroke-width': 1,
23
- })
22
+ "stroke-width": 1,
23
+ });
24
24
  }
@@ -1,20 +1,20 @@
1
- import { el } from './svg.ts'
2
- import { text } from './text.ts'
3
- import { tokens } from '../tokens/index.ts'
1
+ import { el } from "./svg.ts";
2
+ import { text } from "./text.ts";
3
+ import { tokens } from "../tokens/index.ts";
4
4
 
5
5
  export interface QuantityBadgeProps {
6
- x: number
7
- y: number
8
- quantity: number
6
+ x: number;
7
+ y: number;
8
+ quantity: number;
9
9
  }
10
10
 
11
11
  export function quantityBadge({ x, y, quantity }: QuantityBadgeProps): string {
12
- if (quantity <= 1) return ''
13
- const label = `×${quantity}`
14
- const w = label.length * 7 + 12
15
- const h = tokens.spacing.quantityBadgeHeight
12
+ if (quantity <= 1) return "";
13
+ const label = `×${quantity}`;
14
+ const w = label.length * 7 + 12;
15
+ const h = tokens.spacing.quantityBadgeHeight;
16
16
  return (
17
- el('rect', {
17
+ el("rect", {
18
18
  x: x - w,
19
19
  y,
20
20
  width: w,
@@ -31,7 +31,7 @@ export function quantityBadge({ x, y, quantity }: QuantityBadgeProps): string {
31
31
  weight: 700,
32
32
  family: tokens.typography.mono,
33
33
  color: tokens.colors.surface.background,
34
- anchor: 'middle',
34
+ anchor: "middle",
35
35
  })
36
- )
36
+ );
37
37
  }