@realnation/builder-shared-sdk 2.4.0 → 2.4.1

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.
@@ -13,14 +13,7 @@ export * from './imageFit.js';
13
13
  export { wrapText, tokenize, layoutLines, type MeasureText, type TextAlign, type TextVAlign, type PlacedLine, } from './text.js';
14
14
  export { renderSouvenir, drawOrder, pageHasOpaqueBackground, souvenirSignature, loadSouvenirImage, } from './render.js';
15
15
  import { type SouvenirOptions, type SouvenirPage, type SouvenirResult, type SouvenirValues } from './types.js';
16
- /**
17
- * Element ids for output-bound elements are `outputs@@nodeId@@key[@@tab@@rand]`.
18
- * Modules know field keys; the page knows element ids. This bridges the two.
19
- */
20
- export declare function parseSouvenirOutputId(eid: string): {
21
- nodeId: string;
22
- key: string;
23
- } | null;
16
+ export { parseSouvenirOutputId } from './outputId.js';
24
17
  /**
25
18
  * Spreads `{ fieldKey: value }` over every element on the page bound to that
26
19
  * field, so a module never has to know how element ids are built.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/souvenir/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,SAAS,EACT,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAKpB;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKzF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EACtD,MAAM,CAAC,EAAE,MAAM,GACd,cAAc,CAUhB;AAYD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,YAAY,EAClB,MAAM,GAAE,cAAmB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAqBzB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,GAAE,cAAmB,GAAG,MAAM,CAEnF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/souvenir/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,SAAS,EACT,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EACtD,MAAM,CAAC,EAAE,MAAM,GACd,cAAc,CAUhB;AAYD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,YAAY,EAClB,MAAM,GAAE,cAAmB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAqBzB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,GAAE,cAAmB,GAAG,MAAM,CAEnF"}
@@ -14,20 +14,8 @@ export { wrapText, tokenize, layoutLines, } from './text.js';
14
14
  export { renderSouvenir, drawOrder, pageHasOpaqueBackground, souvenirSignature, loadSouvenirImage, } from './render.js';
15
15
  import { pageHasOpaqueBackground, renderSouvenir, souvenirSignature } from './render.js';
16
16
  import { SouvenirError, } from './types.js';
17
- const OUTPUT_SEPARATOR = '@@';
18
- const OUTPUT_PREFIX = 'outputs';
19
- /**
20
- * Element ids for output-bound elements are `outputs@@nodeId@@key[@@tab@@rand]`.
21
- * Modules know field keys; the page knows element ids. This bridges the two.
22
- */
23
- export function parseSouvenirOutputId(eid) {
24
- if (!eid.startsWith(OUTPUT_PREFIX + OUTPUT_SEPARATOR))
25
- return null;
26
- const parts = eid.split(OUTPUT_SEPARATOR);
27
- if (parts.length < 3)
28
- return null;
29
- return { nodeId: parts[1], key: parts[2] };
30
- }
17
+ import { parseSouvenirOutputId } from './outputId.js';
18
+ export { parseSouvenirOutputId } from './outputId.js';
31
19
  /**
32
20
  * Spreads `{ fieldKey: value }` over every element on the page bound to that
33
21
  * field, so a module never has to know how element ids are built.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Element ids for output-bound elements are `outputs@@nodeId@@key[@@tab@@rand]`.
3
+ * Modules know field keys; the page knows element ids. This bridges the two.
4
+ */
5
+ export declare function parseSouvenirOutputId(eid: string): {
6
+ nodeId: string;
7
+ key: string;
8
+ } | null;
9
+ //# sourceMappingURL=outputId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputId.d.ts","sourceRoot":"","sources":["../../src/souvenir/outputId.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKzF"}
@@ -0,0 +1,14 @@
1
+ const OUTPUT_SEPARATOR = '@@';
2
+ const OUTPUT_PREFIX = 'outputs';
3
+ /**
4
+ * Element ids for output-bound elements are `outputs@@nodeId@@key[@@tab@@rand]`.
5
+ * Modules know field keys; the page knows element ids. This bridges the two.
6
+ */
7
+ export function parseSouvenirOutputId(eid) {
8
+ if (!eid.startsWith(OUTPUT_PREFIX + OUTPUT_SEPARATOR))
9
+ return null;
10
+ const parts = eid.split(OUTPUT_SEPARATOR);
11
+ if (parts.length < 3)
12
+ return null;
13
+ return { nodeId: parts[1], key: parts[2] };
14
+ }
@@ -9,7 +9,22 @@
9
9
  * mirrors the runner's `elementStyle`, which is what the operator sees in the
10
10
  * editor and the preview.
11
11
  */
12
- import { type SouvenirOptions, type SouvenirPage, type SouvenirValues } from './types.js';
12
+ import { type SouvenirOptions, type SouvenirPage, type SouvenirProperty, type SouvenirValues } from './types.js';
13
+ /**
14
+ * An element bound to a Data field whose field has no value is left out, the
15
+ * way the runner hides it on a live page. Falling back to what the Builder
16
+ * stored for it would draw the field's name (such as "Message") into the image.
17
+ */
18
+ export declare function isUnfilledOutput(eid: string, values: SouvenirValues): boolean;
19
+ /**
20
+ * The border the runner shows for an element, or null when it shows none:
21
+ * border style None, no width, or a transparent colour. Shapes and every other
22
+ * element share these rules, as they share the runner's `elementStyle`.
23
+ */
24
+ export declare function borderOf(property: SouvenirProperty): {
25
+ width: number;
26
+ color: string;
27
+ } | null;
13
28
  /** Loads one image with EXIF orientation applied, or throws a SouvenirError. */
14
29
  export declare function loadSouvenirImage(url: string, crossOrigin: string | null | undefined): Promise<CanvasImageSource & {
15
30
  width: number;
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/souvenir/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,YAAY,EAEjB,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAC;AAkRpB,gFAAgF;AAChF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAAC,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhE;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAOtD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAYnE;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAMpF;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,MAAM,GAAE,cAAmB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAoE5B"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/souvenir/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAC;AA6CpB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAI7E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO5F;AA6ND,gFAAgF;AAChF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAAC,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhE;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAOtD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAYnE;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAMpF;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,MAAM,GAAE,cAAmB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CAsE5B"}
@@ -10,6 +10,7 @@
10
10
  * editor and the preview.
11
11
  */
12
12
  import { computeImageFitDrawRect, hashSignature, normalizeImageFit } from './imageFit.js';
13
+ import { parseSouvenirOutputId } from './outputId.js';
13
14
  import { layoutLines, wrapText } from './text.js';
14
15
  import { isSouvenirSizeAllowed, SouvenirError, SOUVENIR_ELEMENT_TYPES, } from './types.js';
15
16
  const DEFAULT_LINE_HEIGHT = 1.2;
@@ -40,6 +41,33 @@ function geometryOf(property) {
40
41
  function isHidden(property) {
41
42
  return property['display'] === 'off';
42
43
  }
44
+ /**
45
+ * An element bound to a Data field whose field has no value is left out, the
46
+ * way the runner hides it on a live page. Falling back to what the Builder
47
+ * stored for it would draw the field's name (such as "Message") into the image.
48
+ */
49
+ export function isUnfilledOutput(eid, values) {
50
+ if (!parseSouvenirOutputId(eid))
51
+ return false;
52
+ const value = values[eid];
53
+ return value === undefined || value === null || String(value) === '';
54
+ }
55
+ /**
56
+ * The border the runner shows for an element, or null when it shows none:
57
+ * border style None, no width, or a transparent colour. Shapes and every other
58
+ * element share these rules, as they share the runner's `elementStyle`.
59
+ */
60
+ export function borderOf(property) {
61
+ if (property['border-style'] === 'none')
62
+ return null;
63
+ const width = num(property['border-width']);
64
+ if (width <= 0)
65
+ return null;
66
+ const raw = typeof property['border-color'] === 'string' ? property['border-color'].trim() : '';
67
+ if (raw === TRANSPARENT)
68
+ return null;
69
+ return { width, color: raw || '#94a3b8' };
70
+ }
43
71
  function roundedRectPath(ctx, x, y, width, height, radius) {
44
72
  const r = Math.max(0, Math.min(radius, Math.min(width, height) / 2));
45
73
  ctx.beginPath();
@@ -95,21 +123,16 @@ function fillBackground(ctx, property, width, height, radius, ellipse) {
95
123
  * size the operator gave it, at the cost of half a border width along the edge.
96
124
  */
97
125
  function strokeBorder(ctx, property, width, height, radius, ellipse) {
98
- if (property['border-style'] === 'none')
99
- return;
100
- const borderWidth = num(property['border-width']);
101
- if (borderWidth <= 0)
126
+ const border = borderOf(property);
127
+ if (!border)
102
128
  return;
103
- const color = colorOf(property['border-color']) || '#94a3b8';
104
- if (!color)
105
- return;
106
- const inset = borderWidth / 2;
129
+ const inset = border.width / 2;
107
130
  if (ellipse)
108
- ellipsePath(ctx, inset, inset, width - borderWidth, height - borderWidth);
131
+ ellipsePath(ctx, inset, inset, width - border.width, height - border.width);
109
132
  else
110
- roundedRectPath(ctx, inset, inset, width - borderWidth, height - borderWidth, Math.max(0, radius - inset));
111
- ctx.lineWidth = borderWidth;
112
- ctx.strokeStyle = color;
133
+ roundedRectPath(ctx, inset, inset, width - border.width, height - border.width, Math.max(0, radius - inset));
134
+ ctx.lineWidth = border.width;
135
+ ctx.strokeStyle = border.color;
113
136
  ctx.stroke();
114
137
  }
115
138
  function fontOf(property, fontSize) {
@@ -207,18 +230,9 @@ function drawShape(ctx, type, property, geo) {
207
230
  c.fillStyle = fill;
208
231
  c.fill();
209
232
  }
210
- const strokeWidth = num(property['stroke-width'], 0) || num(property['border-width'], 0);
211
- const strokeColor = colorOf(property['stroke-color']) || colorOf(property['border-color']);
212
- if (strokeWidth > 0 && strokeColor) {
213
- const inset = strokeWidth / 2;
214
- if (ellipse)
215
- ellipsePath(c, inset, inset, width - strokeWidth, height - strokeWidth);
216
- else
217
- roundedRectPath(c, inset, inset, width - strokeWidth, height - strokeWidth, Math.max(0, geo.radius - inset));
218
- c.lineWidth = strokeWidth;
219
- c.strokeStyle = strokeColor;
220
- c.stroke();
221
- }
233
+ // Rectangles and ellipses take their outline from the border settings, like
234
+ // on a live page; a new shape stores a 1px grey border with style None.
235
+ strokeBorder(c, property, width, height, geo.radius, ellipse);
222
236
  });
223
237
  }
224
238
  /** Loads one image with EXIF orientation applied, or throws a SouvenirError. */
@@ -317,6 +331,8 @@ export async function renderSouvenir(page, values = {}, options = {}) {
317
331
  const type = page.elements[eid]?.type;
318
332
  if (type !== 'image' && type !== 'image-frame' && type !== 'bg')
319
333
  return;
334
+ if (isUnfilledOutput(eid, values))
335
+ return;
320
336
  const property = page.properties?.[eid] ?? {};
321
337
  const url = (values[eid] ?? property['source'] ?? '').toString().trim();
322
338
  if (!url)
@@ -335,6 +351,8 @@ export async function renderSouvenir(page, values = {}, options = {}) {
335
351
  const geo = geometryOf(property);
336
352
  if (geo.width <= 0 || geo.height <= 0)
337
353
  continue;
354
+ if (isUnfilledOutput(eid, values))
355
+ continue;
338
356
  if (type === 'text') {
339
357
  const content = (values[eid] ?? property['content'] ?? '').toString();
340
358
  drawText(ctx, property, geo, content);
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
- {
2
- "name": "@realnation/builder-shared-sdk",
3
- "version": "2.4.0",
4
- "type": "module",
5
- "private": false,
6
- "exports": {
7
- "./package.json": "./package.json",
8
- ".": {
9
- "types": "./dist/index.d.ts",
10
- "import": "./dist/index.js"
11
- },
12
- "./runtime": {
13
- "types": "./dist/runtime/index.d.ts",
14
- "import": "./dist/runtime/index.js"
15
- },
16
- "./runtime/vue": {
17
- "types": "./dist/runtime/vue/index.d.ts",
18
- "import": "./dist/runtime/vue/index.js"
19
- },
20
- "./interaction": {
21
- "types": "./dist/interaction/index.d.ts",
22
- "import": "./dist/interaction/index.js"
23
- },
24
- "./interaction/vue": {
25
- "types": "./dist/interaction/vue.d.ts",
26
- "import": "./dist/interaction/vue.js"
27
- },
28
- "./souvenir": {
29
- "types": "./dist/souvenir/index.d.ts",
30
- "import": "./dist/souvenir/index.js"
31
- }
32
- },
33
- "files": [
34
- "dist"
35
- ],
36
- "scripts": {
37
- "build": "tsc -p tsconfig.json",
38
- "clean": "rimraf dist",
39
- "test": "vitest run",
40
- "test:watch": "vitest",
41
- "prepublishOnly": "npm run clean && npm run test && npm run build",
42
- "prepare": "npm run clean && npm run build"
43
- },
44
- "dependencies": {
45
- "ali-oss": "^6.23.0",
46
- "axios": "^1.7.7"
47
- },
48
- "peerDependencies": {
49
- "vue": "^3.4.0"
50
- },
51
- "peerDependenciesMeta": {
52
- "vue": {
53
- "optional": true
54
- }
55
- },
56
- "devDependencies": {
57
- "@types/ali-oss": "^6.16.11",
58
- "@types/node": "^26.1.2",
59
- "rimraf": "^5.0.5",
60
- "typescript": "^5.4.5",
61
- "vitest": "^2.1.0",
62
- "vue": "^3.4.0"
63
- }
64
- }
1
+ {
2
+ "name": "@realnation/builder-shared-sdk",
3
+ "version": "2.4.1",
4
+ "type": "module",
5
+ "private": false,
6
+ "exports": {
7
+ "./package.json": "./package.json",
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./runtime": {
13
+ "types": "./dist/runtime/index.d.ts",
14
+ "import": "./dist/runtime/index.js"
15
+ },
16
+ "./runtime/vue": {
17
+ "types": "./dist/runtime/vue/index.d.ts",
18
+ "import": "./dist/runtime/vue/index.js"
19
+ },
20
+ "./interaction": {
21
+ "types": "./dist/interaction/index.d.ts",
22
+ "import": "./dist/interaction/index.js"
23
+ },
24
+ "./interaction/vue": {
25
+ "types": "./dist/interaction/vue.d.ts",
26
+ "import": "./dist/interaction/vue.js"
27
+ },
28
+ "./souvenir": {
29
+ "types": "./dist/souvenir/index.d.ts",
30
+ "import": "./dist/souvenir/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.json",
38
+ "clean": "rimraf dist",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "prepublishOnly": "npm run clean && npm run test && npm run build",
42
+ "prepare": "npm run clean && npm run build"
43
+ },
44
+ "dependencies": {
45
+ "ali-oss": "^6.23.0",
46
+ "axios": "^1.7.7"
47
+ },
48
+ "peerDependencies": {
49
+ "vue": "^3.4.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "vue": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "devDependencies": {
57
+ "@types/ali-oss": "^6.16.11",
58
+ "@types/node": "^26.1.2",
59
+ "rimraf": "^5.0.5",
60
+ "typescript": "^5.4.5",
61
+ "vitest": "^2.1.0",
62
+ "vue": "^3.4.0"
63
+ }
64
+ }