@design.estate/dees-document 5.6.0 → 6.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design.estate/dees-document",
3
- "version": "5.6.0",
3
+ "version": "6.1.0",
4
4
  "private": false,
5
5
  "description": "A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.",
6
6
  "main": "dist_ts_web/index.js",
@@ -15,31 +15,31 @@
15
15
  "author": "Lossless GmbH",
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
- "@design.estate/dees-domtools": "^3.0.0",
19
- "@design.estate/dees-element": "^3.2.0",
18
+ "@design.estate/dees-domtools": "^5.0.0",
19
+ "@design.estate/dees-element": "^4.0.1",
20
20
  "@fin.cx/calculation": "^1.4.0",
21
21
  "@push.rocks/smartfile": "^13.1.3",
22
22
  "@push.rocks/smartfs": "^1.7.0",
23
23
  "@push.rocks/smartjson": "^6.0.2",
24
24
  "@push.rocks/smartpath": "^6.0.0",
25
- "@push.rocks/smartpdf": "^6.0.1",
25
+ "@push.rocks/smartpdf": "^6.5.0",
26
26
  "@push.rocks/smarttime": "^4.2.3",
27
27
  "@tsclass/tsclass": "^10.2.0",
28
28
  "@types/qrcode": "^1.5.6",
29
29
  "qrcode": "^1.5.4"
30
30
  },
31
31
  "peerDependencies": {
32
- "@design.estate/dees-catalog": "^9.7.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
32
+ "@design.estate/dees-catalog": "^21.0.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@design.estate/dees-catalog": "^19.4.0",
36
- "@design.estate/dees-wcctools": "^3.9.4",
37
- "@git.zone/tsbuild": "^5.1.2",
38
- "@git.zone/tsbundle": "^3.1.3",
39
- "@git.zone/tsrun": "^3.0.2",
40
- "@git.zone/tstest": "^7.0.0",
41
- "@git.zone/tswatch": "^6.0.0",
42
- "@types/node": "^26.6.3"
35
+ "@design.estate/dees-catalog": "^21.0.0",
36
+ "@design.estate/dees-wcctools": "^5.0.0",
37
+ "@git.zone/tsbuild": "^6.0.2",
38
+ "@git.zone/tsbundle": "^3.2.1",
39
+ "@git.zone/tsrun": "^3.0.3",
40
+ "@git.zone/tstest": "^7.2.0",
41
+ "@git.zone/tswatch": "^7.1.1",
42
+ "@types/node": "^26.6.4"
43
43
  },
44
44
  "files": [
45
45
  "ts/**/*",
package/readme.md CHANGED
@@ -12,7 +12,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
12
12
  pnpm install @design.estate/dees-document @design.estate/dees-catalog
13
13
  ```
14
14
 
15
- `@design.estate/dees-catalog` is a peer dependency (9.7 to 19.x): the web components (`@design.estate/dees-document/web`) render the catalog's `dees-icon` and use the catalog copy your application installs. Custom elements are registered globally, so an application must hold one catalog copy, and a component library that brought its own would register each catalog element a second time. pnpm and npm 7+ install a missing peer automatically; Yarn reports it as missing, so list it yourself. The server side (`@design.estate/dees-document`, `/node`) and `/shared` do not load the catalog at runtime: `PdfService` renders with the bundle built into the package.
15
+ `@design.estate/dees-catalog` is a peer dependency (21.x, the catalog on `@design.estate/dees-element` 4): the web components (`@design.estate/dees-document/web`) render the catalog's `dees-icon` and use the catalog copy your application installs. Custom elements are registered globally, so an application must hold one catalog copy, and a component library that brought its own would register each catalog element a second time. pnpm and npm 7+ install a missing peer automatically; Yarn reports it as missing, so list it yourself. The web components run on `@design.estate/dees-element` 4 and `@design.estate/dees-domtools` 5; an application that depends on either itself needs those majors too, or its bundle carries a second copy. The server side (`@design.estate/dees-document`, `/node`) and `/shared` do not load the catalog at runtime: `PdfService` renders with the bundle built into the package.
16
16
 
17
17
  ## Features
18
18
 
@@ -142,7 +142,7 @@ await fs.file('./invoice.pdf').write(Buffer.from(pdfResult.buffer));
142
142
  await pdfService.stop();
143
143
  ```
144
144
 
145
- `createPdfFromLetterObject()` returns the `IPdf` of `@push.rocks/smartpdf` 6: the PDF in `buffer`, and in `metadata.textExtraction` its text as PDF.js reads it, a line break where a printed line ends and a blank line (`PDF_TEXT_PAGE_SEPARATOR` of smartpdf) between pages. PDF generation needs Node.js 22.13 or newer.
145
+ `createPdfFromLetterObject()` returns the `IPdf` of `@push.rocks/smartpdf` 6: the PDF in `buffer`, and in `metadata.textExtraction` its text as PDF.js reads it, a line break where a printed line ends and a blank line (`PDF_TEXT_PAGE_SEPARATOR` of smartpdf) between pages. The page is printed once the document is laid out (it has the attribute `rendered`, see [Web Component Usage](#web-component-usage)); a document that is not within 30 seconds rejects with an error naming the selector it waited for and the errors the page threw. PDF generation needs Node.js 22.13 or newer.
146
146
 
147
147
  ### Document Settings
148
148
 
@@ -214,6 +214,8 @@ html`
214
214
  `;
215
215
  ```
216
216
 
217
+ `<dedocument-dedocument>` lays the letter out on pages after it updates. Once the pages show the current letter and settings, with their images loaded (or failed) and the fonts ready, the element has the attribute `rendered`; a change to the letter or the settings takes it away until the pages show the change. Whatever prints or captures the element waits for `dedocument-dedocument[rendered]`, as `PdfService` does.
218
+
217
219
  ### Translation System
218
220
 
219
221
  The package includes a translation system for multi-language document generation:
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-document',
6
- version: '5.6.0',
6
+ version: '6.1.0',
7
7
  description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
8
8
  }
@@ -66,9 +66,10 @@ export class PdfService {
66
66
  )}"></dedocument-dedocument>
67
67
  `;
68
68
  // console.log(html);
69
- const pdfResult = await this.smartpdfInstance.getA4PdfResultForHtmlString(
70
- html
71
- );
69
+ // the page is printed once the document's pages are done, with their images and fonts
70
+ const pdfResult = await this.smartpdfInstance.getA4PdfResultForHtmlString(html, {
71
+ readySelector: "dedocument-dedocument[rendered]",
72
+ });
72
73
  return pdfResult;
73
74
  }
74
75
  }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-document',
6
- version: '5.6.0',
6
+ version: '6.1.0',
7
7
  description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
8
8
  }
@@ -38,6 +38,42 @@ declare global {
38
38
  }
39
39
  }
40
40
 
41
+ /** the elements under a node, those in shadow roots included */
42
+ const elementsUnder = (rootArg: ParentNode): Element[] => {
43
+ const found: Element[] = [];
44
+ for (const element of Array.from(rootArg.querySelectorAll("*"))) {
45
+ found.push(element);
46
+ if (element.shadowRoot) {
47
+ found.push(...elementsUnder(element.shadowRoot));
48
+ }
49
+ }
50
+ return found;
51
+ };
52
+
53
+ /**
54
+ * Resolves once the pages under a node show what they are given: no element
55
+ * under it has an update pending, its images have loaded or failed, and the
56
+ * fonts are ready.
57
+ */
58
+ const settleRendering = async (rootArg: ParentNode): Promise<void> => {
59
+ for (;;) {
60
+ const pending = elementsUnder(rootArg).filter(
61
+ (element): element is DeesElement =>
62
+ element instanceof DeesElement && element.isUpdatePending
63
+ );
64
+ if (pending.length === 0) {
65
+ break;
66
+ }
67
+ await Promise.all(pending.map((element) => element.updateComplete));
68
+ }
69
+ const images = elementsUnder(rootArg).filter(
70
+ (element): element is HTMLImageElement => element instanceof HTMLImageElement
71
+ );
72
+ // an image that fails is shown as the browser shows it: it settles all the same
73
+ await Promise.all(images.map((image) => image.decode().catch(() => {})));
74
+ await document.fonts.ready;
75
+ };
76
+
41
77
  @customElement("dedocument-dedocument")
42
78
  export class DeDocument extends DeesElement {
43
79
  public static demo = demoFunc;
@@ -121,15 +157,23 @@ export class DeDocument extends DeesElement {
121
157
  return html` <div class="documentContainer"></div> `;
122
158
  }
123
159
 
124
- public async firstUpdated(
125
- _changedProperties: Map<string | number | symbol, unknown>
126
- ) {
127
- domtools.plugins.smartdelay.delayFor(0).then(async () => {
160
+ public willUpdate(
161
+ changedProperties: Map<string | number | symbol, unknown>
162
+ ): void {
163
+ super.willUpdate(changedProperties);
164
+ // the settings the document is first given are completed with the
165
+ // defaults before its first update, so it renders once, with them
166
+ if (!this.hasUpdated) {
128
167
  this.documentSettings = {
129
168
  ...defaultDocumentSettings,
130
169
  ...this.documentSettings,
131
170
  };
132
- });
171
+ }
172
+ }
173
+
174
+ public async firstUpdated(
175
+ _changedProperties: Map<string | number | symbol, unknown>
176
+ ) {
133
177
  const resizeObserver = new ResizeObserver((entries) => {
134
178
  for (const entry of entries) {
135
179
  const width = entry.contentRect.width;
@@ -149,8 +193,19 @@ export class DeDocument extends DeesElement {
149
193
  public latestRenderedLetterData: plugins.tsclass.business.TLetter | null = null;
150
194
  public cleanupStore: HTMLElement[] = [];
151
195
  public paginationInfo: IPagePaginationInfo[] = [];
196
+ /** the renders begun; a render a later one superseded leaves the document to it */
197
+ private renderGeneration = 0;
152
198
 
199
+ /**
200
+ * Lays the letter out on pages. Once the pages show the current letter and
201
+ * settings, with their images loaded and the fonts ready, the element has
202
+ * the attribute `rendered` (what a print of it can wait for); a render begun
203
+ * since takes it away until that render is done.
204
+ */
153
205
  public async renderDocument() {
206
+ const generation = ++this.renderGeneration;
207
+ const isCurrent = () => generation === this.renderGeneration;
208
+ this.removeAttribute("rendered");
154
209
  // a document whose advance payments do not add up to its paid amount, or
155
210
  // whose accent colour is no hex colour, is refused: no page of it, and none
156
211
  // of the letter before it, stays on screen. The accent is checked here,
@@ -168,7 +223,7 @@ export class DeDocument extends DeesElement {
168
223
  }
169
224
  this.latestDocumentSettings = this.documentSettings;
170
225
  this.latestRenderedLetterData = this.letterData;
171
- this.paginationInfo = [];
226
+ const paginationInfo: IPagePaginationInfo[] = [];
172
227
 
173
228
  const cleanUpStoreCurrentRender: HTMLElement[] = [];
174
229
  const cleanUpStoreNextRender: HTMLElement[] = [];
@@ -189,7 +244,7 @@ export class DeDocument extends DeesElement {
189
244
  content.documentSettings = this.documentSettings;
190
245
  document.body.appendChild(content);
191
246
 
192
- await domtools.convenience.smartdelay.delayFor(0);
247
+ await domtools.convenience.smartpromise.delayFor(0);
193
248
  let overallContentOffset: number = 0;
194
249
  let currentContentOffset: number;
195
250
  let trimmed: number;
@@ -224,7 +279,7 @@ export class DeDocument extends DeesElement {
224
279
  overallContentOffset = overallContentOffset + currentContentOffset;
225
280
 
226
281
  // Track pagination info for this page
227
- this.paginationInfo.push({
282
+ paginationInfo.push({
228
283
  pageNumber: pageCounter,
229
284
  startOffset: pageStartOffset,
230
285
  contentLength: currentContentOffset,
@@ -235,6 +290,15 @@ export class DeDocument extends DeesElement {
235
290
  }
236
291
  }
237
292
 
293
+ if (!isCurrent()) {
294
+ // a later render shows the document: this one's pages are dropped
295
+ for (const cleanUp of [...cleanUpStoreCurrentRender, ...cleanUpStoreNextRender]) {
296
+ cleanUp.remove();
297
+ }
298
+ return;
299
+ }
300
+ this.paginationInfo = paginationInfo;
301
+
238
302
  for (const cleanUp of this.cleanupStore) {
239
303
  cleanUp.remove();
240
304
  }
@@ -275,6 +339,11 @@ export class DeDocument extends DeesElement {
275
339
  bubbles: true,
276
340
  composed: true,
277
341
  }));
342
+
343
+ await settleRendering(documentContainer);
344
+ if (isCurrent()) {
345
+ this.toggleAttribute("rendered", true);
346
+ }
278
347
  }
279
348
 
280
349
  async updated(