@quario/viewer 0.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/CHANGELOG.md +17 -0
- package/LICENSE +219 -0
- package/README.md +303 -0
- package/lib/button.js +88 -0
- package/lib/check.js +154 -0
- package/lib/chrome.js +187 -0
- package/lib/index.d.ts +130 -0
- package/lib/index.js +450 -0
- package/lib/mark.js +40 -0
- package/lib/menu.js +318 -0
- package/lib/panel.js +126 -0
- package/lib/register.d.ts +9 -0
- package/lib/register.js +10 -0
- package/lib/stage.js +361 -0
- package/lib/style.js +94 -0
- package/lib/toolbar.js +95 -0
- package/lib/zoom.js +45 -0
- package/package.json +78 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to @quario/viewer are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-08-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **An embeddable `<quario-viewer>`.** Assign a compiled report, the targets
|
|
15
|
+
to render with, and data; it shows the HTML report on a continuous sheet,
|
|
16
|
+
with zoom, fit, and export buttons for the targets you passed. It is not a
|
|
17
|
+
target and never walks the event stream.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Quario License Agreement
|
|
2
|
+
|
|
3
|
+
> **In short:** Quario is commercial software with visible source.
|
|
4
|
+
> Evaluate it free for as long as you like. Evaluation output is
|
|
5
|
+
> watermarked. Anything else needs a per-developer license, after
|
|
6
|
+
> which deployment is unlimited and royalty-free. This summary is not
|
|
7
|
+
> the license; the full terms below govern.
|
|
8
|
+
|
|
9
|
+
Version 1.0, August 2026
|
|
10
|
+
|
|
11
|
+
This agreement is between you and Webstronauts, trading as Quario,
|
|
12
|
+
established in the Netherlands ("we", "us"), and governs the Software
|
|
13
|
+
defined below.
|
|
14
|
+
|
|
15
|
+
Quario is commercial software. Its packages ship with readable source
|
|
16
|
+
code, but it is not open source. No rights are granted beyond those in
|
|
17
|
+
this agreement.
|
|
18
|
+
|
|
19
|
+
## 1. Definitions
|
|
20
|
+
|
|
21
|
+
- The **Software** is any software we publish under the Quario name,
|
|
22
|
+
including the `quario` and `@quario/*` packages, except where a
|
|
23
|
+
product states its own terms. Each release states the edition of this
|
|
24
|
+
agreement that governs it.
|
|
25
|
+
- **You** means the individual exercising rights under this agreement
|
|
26
|
+
and, where that individual acts for a company or other organization,
|
|
27
|
+
that organization. In that case the organization is the licensee, and
|
|
28
|
+
the rights and limits in this agreement apply to it as a whole.
|
|
29
|
+
- An **Application** is software you create that incorporates the
|
|
30
|
+
Software and adds substantial functionality beyond it. A
|
|
31
|
+
licensee-operated service that embeds the Software and renders
|
|
32
|
+
reports for your End Users is an Application under this agreement,
|
|
33
|
+
even if it does little more than render.
|
|
34
|
+
- A **Developer** is any individual (employee or contractor working on
|
|
35
|
+
your behalf) who writes or maintains code that uses the Software's
|
|
36
|
+
APIs, or who creates or maintains report definitions for use with the
|
|
37
|
+
Software. That includes people who never install or run the Software
|
|
38
|
+
themselves, when their definitions are rendered by a service that
|
|
39
|
+
embeds it.
|
|
40
|
+
- A **Seat** is a paid license for one named Developer, purchased from
|
|
41
|
+
us or an authorized reseller.
|
|
42
|
+
- **End Users** are people who interact with the Software only through
|
|
43
|
+
an Application: running it, viewing the reports it produces, or
|
|
44
|
+
supplying parameters or other input to existing report definitions
|
|
45
|
+
the Application provides. Creating or maintaining report definitions
|
|
46
|
+
makes someone a Developer. End Users do not purchase a license; their
|
|
47
|
+
rights come from section 5.
|
|
48
|
+
|
|
49
|
+
## 2. Acceptance
|
|
50
|
+
|
|
51
|
+
This file is our standing offer. Anyone who exercises the evaluation
|
|
52
|
+
rights in section 3 accepts them, subject to their conditions. All
|
|
53
|
+
other rights arise when Seats are purchased. Where a purchase is made
|
|
54
|
+
for an organization, the person completing it confirms that they are
|
|
55
|
+
authorized to accept this agreement on the organization's behalf, and
|
|
56
|
+
acceptance covers the whole organization. If you do not accept this
|
|
57
|
+
agreement, do not use the Software.
|
|
58
|
+
|
|
59
|
+
## 3. Evaluation
|
|
60
|
+
|
|
61
|
+
You may download, install, and use the Software free of charge to
|
|
62
|
+
evaluate it: assessing whether it suits your purposes, and building
|
|
63
|
+
prototypes or proofs of concept. Evaluation covers the full feature
|
|
64
|
+
set, needs no key, and has no fixed time limit. The Software marks
|
|
65
|
+
evaluation output as described in section 6.
|
|
66
|
+
|
|
67
|
+
Evaluation is limited to assessment: you may not distribute any
|
|
68
|
+
Application containing the Software, use the Software in live business
|
|
69
|
+
operations, or expose it in services offered to third parties. The
|
|
70
|
+
moment your use goes beyond evaluating the Software, it requires Seats
|
|
71
|
+
under section 4.
|
|
72
|
+
|
|
73
|
+
## 4. Developer licenses
|
|
74
|
+
|
|
75
|
+
Any use of the Software beyond section 3 requires a Seat for every
|
|
76
|
+
Developer, whether or not an Application has shipped. With those Seats
|
|
77
|
+
in place:
|
|
78
|
+
|
|
79
|
+
- Each licensed Developer may install and use the Software on any number
|
|
80
|
+
of machines for developing Applications.
|
|
81
|
+
- Automated build, test, and CI systems do not consume Seats.
|
|
82
|
+
- Seats are per named individual. They may be reassigned when a
|
|
83
|
+
Developer stops working with the Software, but not shared or pooled
|
|
84
|
+
between concurrently active Developers. Contractors' work for you is
|
|
85
|
+
covered by your Seats; work they do for other clients is not.
|
|
86
|
+
|
|
87
|
+
## 5. Distribution and runtime
|
|
88
|
+
|
|
89
|
+
With Seats under section 4 in place, you may deploy and operate your
|
|
90
|
+
Applications yourself, and distribute the Software as an embedded part
|
|
91
|
+
of them, **royalty-free**: no additional fees or per-deployment
|
|
92
|
+
licenses are required for any number of servers, copies, or End Users.
|
|
93
|
+
You may embed your license key in an Application and ship it at no
|
|
94
|
+
charge, so section 6 does not mark that Application's output.
|
|
95
|
+
|
|
96
|
+
For that purpose, we grant you the right to pass through to each End
|
|
97
|
+
User, and to anyone who distributes or operates your Application on your
|
|
98
|
+
behalf, a limited, non-exclusive, non-transferable right to execute the
|
|
99
|
+
Software only as embedded in the Application. You must not permit them
|
|
100
|
+
to extract the Software from the Application or use it to develop
|
|
101
|
+
software of their own.
|
|
102
|
+
|
|
103
|
+
If you operate a service that embeds the Software and renders reports
|
|
104
|
+
for your End Users, that service is an Application under section 1. You
|
|
105
|
+
may run it royalty-free under this section, and you may put your license
|
|
106
|
+
key in it as in any Application. Calling applications that only request
|
|
107
|
+
renders from it need not incorporate the Software. People who use those
|
|
108
|
+
applications only as End Users under section 1 get the pass-through
|
|
109
|
+
rights above.
|
|
110
|
+
|
|
111
|
+
You may not:
|
|
112
|
+
|
|
113
|
+
- distribute the Software on its own, or as part of a product whose
|
|
114
|
+
primary purpose is to provide report-engine, report-design, or
|
|
115
|
+
document-generation capability to third-party developers;
|
|
116
|
+
- expose the Software's APIs to third parties as a development tool or
|
|
117
|
+
service that substitutes for those parties buying their own Seats. A
|
|
118
|
+
licensee-operated service that embeds the Software and renders for
|
|
119
|
+
your own End Users falls outside that ban;
|
|
120
|
+
- fork, mirror, repackage, rename, or wrap the Software, or republish
|
|
121
|
+
it on any package registry or code-hosting platform, other than as
|
|
122
|
+
embedded in an Application.
|
|
123
|
+
|
|
124
|
+
For as long as this agreement is in force, you may not create any tool,
|
|
125
|
+
library, product, or service that competes with the Software or any
|
|
126
|
+
other Quario product, whether the competition is direct or indirect;
|
|
127
|
+
this restriction does not survive termination. You may not remove or
|
|
128
|
+
alter copyright or license notices in the Software. If your Application
|
|
129
|
+
is itself a closed development product for third-party developers (OEM
|
|
130
|
+
redistribution), contact us for separate terms.
|
|
131
|
+
|
|
132
|
+
## 6. License keys and output marking
|
|
133
|
+
|
|
134
|
+
The Software accepts a license key that evidences your Seats under
|
|
135
|
+
section 4. Key checks run offline.
|
|
136
|
+
|
|
137
|
+
Without a valid key, or with a key whose validity period does not cover
|
|
138
|
+
the installed version's release date, the Software runs with its full
|
|
139
|
+
feature set but marks its output as unlicensed. Using a key you are not
|
|
140
|
+
entitled to is a material breach of this agreement.
|
|
141
|
+
|
|
142
|
+
You may not remove, disable, or circumvent the key check or the output
|
|
143
|
+
marking, or distribute anything that does.
|
|
144
|
+
|
|
145
|
+
## 7. Term, updates, and perpetual fallback
|
|
146
|
+
|
|
147
|
+
Seats are sold as an annual subscription. While the subscription is
|
|
148
|
+
active, licensed Developers receive all Software versions we release,
|
|
149
|
+
and support for the Software through our issue tracker and by email. We
|
|
150
|
+
provide support with commercially reasonable effort; we do not guarantee
|
|
151
|
+
specific response times.
|
|
152
|
+
|
|
153
|
+
If the subscription lapses, your license to the versions released during
|
|
154
|
+
your subscription is **perpetual, for the number of Seats held at the
|
|
155
|
+
lapse**: up to that many Developers may keep using those versions, and
|
|
156
|
+
your Applications may keep shipping them, forever, at no further cost.
|
|
157
|
+
Versions released after the lapse require renewal. We encode this rule
|
|
158
|
+
in purchased keys: a key remains valid for every version released
|
|
159
|
+
during its validity period, with no end date.
|
|
160
|
+
|
|
161
|
+
## 8. Source availability
|
|
162
|
+
|
|
163
|
+
The Software's packages ship with readable source code so that you can
|
|
164
|
+
review and debug what you run. Readable source does not grant extra
|
|
165
|
+
rights: this agreement governs all use of the source, and you may
|
|
166
|
+
publish the Software (modified or not) only as part of an Application
|
|
167
|
+
under section 5.
|
|
168
|
+
|
|
169
|
+
## 9. Ownership, trademarks, and feedback
|
|
170
|
+
|
|
171
|
+
We license the Software; we retain all intellectual property rights in
|
|
172
|
+
it. Report definitions, data, and output you create with the Software
|
|
173
|
+
are yours. "Quario" and the Quario logo are our trademarks; this
|
|
174
|
+
agreement grants no rights to use them beyond identifying the Software.
|
|
175
|
+
If you send us feedback or suggestions, we may use them without
|
|
176
|
+
obligation.
|
|
177
|
+
|
|
178
|
+
## 10. Warranty and liability
|
|
179
|
+
|
|
180
|
+
We provide the Software as is. To the fullest extent the law allows, we
|
|
181
|
+
disclaim all warranties and conditions, express or implied, including
|
|
182
|
+
merchantability, fitness for a particular purpose, and non-infringement.
|
|
183
|
+
|
|
184
|
+
To the fullest extent the law allows: we are not liable for indirect or
|
|
185
|
+
consequential damages, loss of profits, loss of data, or business
|
|
186
|
+
interruption; and our total aggregate liability under this agreement is
|
|
187
|
+
limited to the greater of EUR 100 and the fees you paid us in the
|
|
188
|
+
twelve months preceding the event giving rise to the claim. These
|
|
189
|
+
limitations do not apply where the damage results from the intent or
|
|
190
|
+
deliberate recklessness (opzet of bewuste roekeloosheid) of us or our
|
|
191
|
+
management, or where mandatory law does not permit them.
|
|
192
|
+
|
|
193
|
+
## 11. Termination and survival
|
|
194
|
+
|
|
195
|
+
If you materially breach this agreement, we may terminate it by notice.
|
|
196
|
+
If the breach can be remedied, we will first allow 30 days from that
|
|
197
|
+
notice to remedy it, and termination takes effect only if the breach
|
|
198
|
+
remains. On termination, the licenses granted here end.
|
|
199
|
+
Perpetual-fallback rights under section 7 survive termination only if
|
|
200
|
+
they arose from a lapse that predates the breach, and section 5
|
|
201
|
+
survives with them to the extent needed to keep exercising them.
|
|
202
|
+
Sections 8 through 12 survive any termination.
|
|
203
|
+
|
|
204
|
+
## 12. General
|
|
205
|
+
|
|
206
|
+
Dutch law governs this agreement. Disputes go to the competent court for
|
|
207
|
+
our registered office. You may not assign this agreement without our
|
|
208
|
+
consent, except to a successor of your whole business; we may assign it
|
|
209
|
+
to a successor of ours. If a provision is unenforceable, the remainder
|
|
210
|
+
stays in force. Failure to enforce a provision does not waive it. This
|
|
211
|
+
agreement, together with your order for Seats, is the entire agreement
|
|
212
|
+
about the Software, unless we agree otherwise in writing.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
**Purchasing and contact:** <contact@getquario.com> ·
|
|
217
|
+
<https://getquario.com>
|
|
218
|
+
|
|
219
|
+
Copyright © 2026 Webstronauts. All rights reserved.
|
package/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# @quario/viewer
|
|
2
|
+
|
|
3
|
+
**The embeddable report viewer for [quario](https://www.npmjs.com/package/quario), as a custom
|
|
4
|
+
element.** Write `<quario-viewer>`, assign it a compiled report, the targets to render with, and
|
|
5
|
+
data; it displays the rendered report on a white sheet in its shadow root and offers export
|
|
6
|
+
downloads for the exportable targets you passed.
|
|
7
|
+
|
|
8
|
+
The viewer is not a render target and compiles nothing. You pass the configured targets you want;
|
|
9
|
+
the viewer displays the `"html"` target's output and wires an export button to each exportable
|
|
10
|
+
target in the list (`"pdf"`, `"xlsx"`, `"csv"`). The bar always carries the zoom control; with no
|
|
11
|
+
exportable target it carries nothing else.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install quario @quario/html @quario/viewer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The engine is a peer; the viewer itself depends on no target package. Install the targets you want
|
|
20
|
+
beside it (`@quario/pdf`, `@quario/xlsx`, `@quario/csv`) and pass them in. Its own runtime dependency
|
|
21
|
+
is [Lit](https://lit.dev) (`lit` + `@lit/task`), plain ESM like everything else here. ESM-only, and
|
|
22
|
+
browser-only by nature: the element needs a DOM. CSP-safe like the rest of quario. No
|
|
23
|
+
string-to-code paths, chrome styled through constructed stylesheets, so your `style-src` never
|
|
24
|
+
sees a style tag.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { csv } from "@quario/csv";
|
|
30
|
+
import { html } from "@quario/html";
|
|
31
|
+
import { pdf } from "@quario/pdf";
|
|
32
|
+
import { xlsx } from "@quario/xlsx";
|
|
33
|
+
import { quario } from "quario";
|
|
34
|
+
import "@quario/viewer/register";
|
|
35
|
+
|
|
36
|
+
const view = document.querySelector("quario-viewer");
|
|
37
|
+
view.report = quario().report(schema, funcs);
|
|
38
|
+
view.targets = [html(), pdf(), xlsx(), csv()];
|
|
39
|
+
view.data = data;
|
|
40
|
+
view.filename = "sales";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
with `<quario-viewer></quario-viewer>` in your markup, sized by your own CSS. The element is
|
|
44
|
+
`display: block`. Importing `@quario/viewer` defines nothing: the main entry exports the
|
|
45
|
+
`QuarioViewer` class and is side-effect-free, and `@quario/viewer/register` performs the one-line
|
|
46
|
+
`customElements.define("quario-viewer", QuarioViewer)`. A host that wants its own tag imports the
|
|
47
|
+
class and defines it itself. The repository ships this wiring as a runnable page at
|
|
48
|
+
`example/viewer.js`.
|
|
49
|
+
|
|
50
|
+
React 19, Vue and Svelte set properties on custom elements directly, so there is no wrapper
|
|
51
|
+
package to install. See [Using with React, Vue and Svelte](#using-with-react-vue-and-svelte).
|
|
52
|
+
|
|
53
|
+
## Properties
|
|
54
|
+
|
|
55
|
+
| Property | Takes | Default |
|
|
56
|
+
| ------------- | -------------------------------------------- | ----------- |
|
|
57
|
+
| `report` | A compiled report from a quario instance | - |
|
|
58
|
+
| `targets` | Target objects; one named `"html"` required | - |
|
|
59
|
+
| `data` | The render document | `undefined` |
|
|
60
|
+
| `zoom` | `"fit"` or a percentage between 25 and 200 | `"fit"` |
|
|
61
|
+
| `page` | Sheet geometry (the pdf target's vocabulary) | A4, 54pt |
|
|
62
|
+
| `filename` | Export download name, without extension | `"report"` |
|
|
63
|
+
| `colorScheme` | `"light"`, `"dark"`, or `"auto"` | `"light"` |
|
|
64
|
+
|
|
65
|
+
Properties, not attributes: `report`, `targets`, `data` and `page` are values no attribute could
|
|
66
|
+
carry. `targets` mirrors `report.render(target, data)`: the `"html"` target renders the sheet, and
|
|
67
|
+
`"pdf"`/`"xlsx"`/`"csv"` targets become export buttons, in the order given. The quario instance (and
|
|
68
|
+
with it the license and the registry) stays yours: the element takes the compiled report, never a
|
|
69
|
+
schema.
|
|
70
|
+
|
|
71
|
+
Assigning `data` (or any of the others) re-renders. Rapid successive writes render only the newest
|
|
72
|
+
state: a slow render can never overwrite a newer one, and a superseded render fires no event.
|
|
73
|
+
Assignments are compared by identity, so to re-render from the same object, assign a fresh one
|
|
74
|
+
(`view.data = { ...data }`).
|
|
75
|
+
|
|
76
|
+
`page` is the pdf target's vocabulary and defaults, and it is **purely visual**: it sizes the white
|
|
77
|
+
sheet (width and padding) the browser lays the report out on. The PDF target's own geometry lives
|
|
78
|
+
in the host's `pdf({ page })` configuration. Pass the same values to both so preview and export
|
|
79
|
+
agree.
|
|
80
|
+
|
|
81
|
+
## Events and `renderComplete`
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
view.addEventListener("rendered", () => {});
|
|
85
|
+
view.addEventListener("error", ({ detail: { error, kind } }) => {});
|
|
86
|
+
await view.renderComplete; // true when the newest render reached the sheet
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`rendered` fires each time a render reaches the sheet; `error` fires for every failure you should
|
|
90
|
+
know about, with `detail.kind` naming which: `"mount-render"` until a render has ever landed,
|
|
91
|
+
`"update-render"` after, `"export"` for a download that could not be produced. Both events are
|
|
92
|
+
non-bubbling, like `<img>`'s. Listen on the element. (Because the event is named `error`, an
|
|
93
|
+
inline `onerror` attribute on the element would fire too; `window.onerror` never sees it.)
|
|
94
|
+
|
|
95
|
+
`renderComplete` awaits the newest render settling: `true` when it reached the sheet, `false` when
|
|
96
|
+
it failed or there was nothing to render. It never rejects; failures arrive on the `error` event.
|
|
97
|
+
Like every outcome here, it answers for the newest render only. A superseded render's failure is
|
|
98
|
+
reported to no one.
|
|
99
|
+
|
|
100
|
+
Your own mistakes surface on the same channel: a report that is not compiled, a missing
|
|
101
|
+
`"html"` target, or a malformed option property becomes a `TypeError` naming the property, on
|
|
102
|
+
the `error` event and the [error panel](#errors).
|
|
103
|
+
|
|
104
|
+
## Lifecycle
|
|
105
|
+
|
|
106
|
+
There is no `destroy()`. Removing the element from the DOM abandons in-flight work and releases
|
|
107
|
+
its observers; an export that settles after removal downloads nothing. The properties persist, and
|
|
108
|
+
re-inserting the element re-renders from them, so reparenting is safe, and discarding a viewer is
|
|
109
|
+
just discarding the element. Two viewers, or a viewer beside your own components, coexist: each
|
|
110
|
+
element owns its own shadow root.
|
|
111
|
+
|
|
112
|
+
## Using with React, Vue and Svelte
|
|
113
|
+
|
|
114
|
+
No wrapper packages: each framework sets properties on custom elements directly, so the samples
|
|
115
|
+
below are the whole integration. All three were run before landing here. Import
|
|
116
|
+
`@quario/viewer/register` once, anywhere before the component mounts, and size the element with
|
|
117
|
+
your own CSS.
|
|
118
|
+
|
|
119
|
+
### React 19
|
|
120
|
+
|
|
121
|
+
React 19 assigns non-primitive props on a custom element as properties, and `on*` props attach
|
|
122
|
+
listeners for the element's own events. `onrendered` and `onerror` below listen for `rendered`
|
|
123
|
+
and `error`:
|
|
124
|
+
|
|
125
|
+
```jsx
|
|
126
|
+
import "@quario/viewer/register";
|
|
127
|
+
|
|
128
|
+
function Report({ report, targets, data }) {
|
|
129
|
+
return (
|
|
130
|
+
<quario-viewer
|
|
131
|
+
report={report}
|
|
132
|
+
targets={targets}
|
|
133
|
+
data={data}
|
|
134
|
+
filename="sales"
|
|
135
|
+
onrendered={() => console.log("landed")}
|
|
136
|
+
onerror={(event) => console.error(event.detail.kind, event.detail.error)}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Re-rendering with a new `data` prop re-renders the report; the newest write wins, as always.
|
|
143
|
+
(React 18 and earlier stringify unknown props to attributes. There, hold a `ref` and assign the
|
|
144
|
+
properties and listeners in an effect.)
|
|
145
|
+
|
|
146
|
+
### Vue 3
|
|
147
|
+
|
|
148
|
+
`:prop` bindings on a custom element land as properties whenever the element defines them, and
|
|
149
|
+
`@rendered`/`@error` are plain DOM listeners:
|
|
150
|
+
|
|
151
|
+
```vue
|
|
152
|
+
<script setup>
|
|
153
|
+
import "@quario/viewer/register";
|
|
154
|
+
|
|
155
|
+
defineProps(["report", "targets", "data"]);
|
|
156
|
+
const onRendered = () => console.log("landed");
|
|
157
|
+
const onError = (event) => console.error(event.detail.kind, event.detail.error);
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<template>
|
|
161
|
+
<quario-viewer
|
|
162
|
+
:report="report"
|
|
163
|
+
:targets="targets"
|
|
164
|
+
:data="data"
|
|
165
|
+
filename="sales"
|
|
166
|
+
@rendered="onRendered"
|
|
167
|
+
@error="onError"
|
|
168
|
+
></quario-viewer>
|
|
169
|
+
</template>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Tell Vue's template compiler the tag is a custom element, so it does not warn about an unresolved
|
|
173
|
+
component. In Vite:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === "quario-viewer" } } });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Svelte 5
|
|
180
|
+
|
|
181
|
+
Svelte sets a property whenever the element defines one, and `on<name>` attributes are plain DOM
|
|
182
|
+
listeners:
|
|
183
|
+
|
|
184
|
+
```svelte
|
|
185
|
+
<script>
|
|
186
|
+
import "@quario/viewer/register";
|
|
187
|
+
|
|
188
|
+
let { report, targets, data } = $props();
|
|
189
|
+
</script>
|
|
190
|
+
|
|
191
|
+
<quario-viewer
|
|
192
|
+
{report}
|
|
193
|
+
{targets}
|
|
194
|
+
{data}
|
|
195
|
+
filename="sales"
|
|
196
|
+
onrendered={() => console.log("landed")}
|
|
197
|
+
onerror={(event) => console.error(event.detail.kind, event.detail.error)}
|
|
198
|
+
></quario-viewer>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Zoom
|
|
202
|
+
|
|
203
|
+
The bar's magnifier opens the zoom menu: **Fit sheet** on its own, then 25%, 50%, 75%, 100%, 150%
|
|
204
|
+
and 200%. A check marks the current mode — Fit sheet whenever the viewer is fitting, whatever
|
|
205
|
+
percentage that came out at — and the percentage on screen is what the trigger is named after
|
|
206
|
+
("Zoom, 62%"), so nothing in the bar changes width as it moves. The `zoom` property picks the mode
|
|
207
|
+
the viewer opens in: a percentage between 25 and 200 — continuous, not one of the menu's stops — or
|
|
208
|
+
`"fit"`, the default. A percentage the menu does not offer leaves every row unchecked.
|
|
209
|
+
|
|
210
|
+
Fit sizes the sheet to the viewer's width and **only ever shrinks**: given room to spare it stops at
|
|
211
|
+
100%, so the report is shown at its true point size rather than blown up. It has no floor, so a
|
|
212
|
+
narrow pane fits at whatever percentage that takes — below 25% the menu has no row to return to it,
|
|
213
|
+
and **Fit sheet** is the only way back. A fitted viewer follows its own box, so a collapsing panel
|
|
214
|
+
or a resized window re-fits on its own.
|
|
215
|
+
|
|
216
|
+
The preview **scales; it never reflows.** Zooming shrinks or enlarges the rendered sheet like a
|
|
217
|
+
photograph. Line breaks, column widths and point sizes stay what they are at 100%, so the sheet
|
|
218
|
+
stays the document the pdf target will page. Pass the same `page` values to preview and export so
|
|
219
|
+
they agree.
|
|
220
|
+
|
|
221
|
+
## What the preview is
|
|
222
|
+
|
|
223
|
+
On-screen layout is the browser's: one continuous sheet, no pagination. The PDF export paginates
|
|
224
|
+
independently in the pdf target. The preview approximates; the exports are exact.
|
|
225
|
+
|
|
226
|
+
The sheet is the html target's fragment, so a report with image items shows them through that
|
|
227
|
+
target's `data:` URIs. A host page with a Content Security Policy needs `img-src data:` for them
|
|
228
|
+
to display, the same deployment requirement the fragment carries anywhere else.
|
|
229
|
+
|
|
230
|
+
Rendering an unlicensed evaluation, rotated watermarks sit on the sheet — one per page-sized band
|
|
231
|
+
of the continuous preview — and scroll and zoom with the report, the way the PDF export marks every
|
|
232
|
+
page. The wording arrives inside the html target's output, on its `.q-unlicensed` badge; the viewer
|
|
233
|
+
hides that element from sight (keeping it readable by assistive technology) and draws the stamps on
|
|
234
|
+
the sheet itself. A licensed render carries neither.
|
|
235
|
+
|
|
236
|
+
While a render is in flight a thin indeterminate bar sits on the toolbar's bottom edge, and the
|
|
237
|
+
viewer reads `aria-busy="true"`. It reports that the viewer is working, not how far along. The
|
|
238
|
+
engine streams events and cannot know how many are still coming.
|
|
239
|
+
|
|
240
|
+
There is no Print button, because the sheet is the wrong thing to print. Your page's stylesheets do
|
|
241
|
+
not cross into the shadow root, so `@media print` rules never reach the report; and above or below
|
|
242
|
+
100% the sheet is under a `transform: scale()`, so printing the page puts the viewer's chrome on
|
|
243
|
+
paper at whatever zoom the reader happened to leave it. What a printer wants is the pdf target's
|
|
244
|
+
document, the same bytes the PDF export hands over. A host that wants its own Print button owns
|
|
245
|
+
two lines:
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
// `pdfTarget` is the pdf() from the quick start above, and `data` whatever you
|
|
249
|
+
// last rendered — so what prints is exactly what the PDF export would produce.
|
|
250
|
+
const bytes = await rpt.render(pdfTarget, data);
|
|
251
|
+
window.open(URL.createObjectURL(new Blob([bytes], { type: "application/pdf" })));
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
That opens the report in a tab, where the browser's own PDF viewer prints it, paginated as
|
|
255
|
+
the export is, watermark and all.
|
|
256
|
+
|
|
257
|
+
## Errors
|
|
258
|
+
|
|
259
|
+
When a render or an export fails, the viewer says so on the **error panel**, a strip across the top
|
|
260
|
+
of the sheet carrying a short label and the error's own message. It replaces rather than stacks, is
|
|
261
|
+
dismissed by its own button, and is cleared by the next render that reaches the sheet. A successful
|
|
262
|
+
export leaves it up, because the panel describes what you are looking at and a download says
|
|
263
|
+
nothing about that.
|
|
264
|
+
|
|
265
|
+
Every failure the panel draws, and every export failure, also fires the `error` event; nothing is
|
|
266
|
+
rethrown to the platform behind it. Failures after the element is removed are not reported, and a
|
|
267
|
+
render that a newer one has already superseded reports to no one at all.
|
|
268
|
+
|
|
269
|
+
The message carries quario's **located error**, the band/item path and the offending source, as in
|
|
270
|
+
`detail[0] [{{ @.amount.toFixed(2) }}]: ...`. Showing it is safe because report definitions are
|
|
271
|
+
trusted configuration; report _data_ never is, and none of it appears in the path. What can carry
|
|
272
|
+
data is the message itself, if your own registry functions interpolate a row into what they throw.
|
|
273
|
+
That is your call, and the panel puts it on screen as text, never as markup. Compile errors are not
|
|
274
|
+
part of this: `q.report(schema)` raises those before the viewer is ever handed a report.
|
|
275
|
+
|
|
276
|
+
## Color scheme
|
|
277
|
+
|
|
278
|
+
`colorScheme` paints the **chrome** — backdrop, bar, controls, progress strip, error
|
|
279
|
+
panel — and never the sheet. `"light"` (the default) and `"dark"` pin; `"auto"` follows
|
|
280
|
+
the OS via CSS `color-scheme`. The sheet stays white, watermark included.
|
|
281
|
+
|
|
282
|
+
Chrome styles live on `--qv-*` custom properties under stable `qv-*` class names. A
|
|
283
|
+
token set on the element (or an ancestor) always wins over the pin. The names are a
|
|
284
|
+
reference seam, not a frozen vocabulary: backdrop and bar (`--qv-backdrop`, `--qv-bar`,
|
|
285
|
+
`--qv-border`, `--qv-text`), controls (`--qv-icon`, `--qv-icon-active`, `--qv-hover`,
|
|
286
|
+
`--qv-active`, `--qv-focus`), progress (`--qv-progress`), the error panel (`--qv-error`,
|
|
287
|
+
`--qv-error-text`, `--qv-error-border`, `--qv-error-hover`), and the sheet's edge
|
|
288
|
+
(`--qv-sheet-shadow`). `--qv-mark` is the watermark face and does not follow the scheme.
|
|
289
|
+
|
|
290
|
+
The report itself uses the viewer's built-in `q-*` stylesheet, the screen sibling of
|
|
291
|
+
`@quario/html/style.css`.
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
|
|
296
|
+
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
297
|
+
|
|
298
|
+
Pass your license key once, on the instance; it is verified offline:
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
const q = quario({ license: "quario_..." });
|
|
302
|
+
await q.license; // { licensed: true, licensee: "Acme BV", id: "1-ACME" }
|
|
303
|
+
```
|
package/lib/button.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared `qv-button` template. Every control the viewer renders comes
|
|
3
|
+
* from here — the zoom menu's trigger, the export downloads, the error
|
|
4
|
+
* panel's dismiss — so they agree on the class, the `type` and how they are
|
|
5
|
+
* named.
|
|
6
|
+
*/
|
|
7
|
+
import { css, html, nothing } from "lit";
|
|
8
|
+
|
|
9
|
+
export let BUTTON = css`
|
|
10
|
+
.qv-button {
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
width: 30px;
|
|
15
|
+
height: 28px;
|
|
16
|
+
padding: 0;
|
|
17
|
+
border: none;
|
|
18
|
+
border-radius: 5px;
|
|
19
|
+
background: transparent;
|
|
20
|
+
color: var(--_icon);
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.qv-button:hover {
|
|
25
|
+
background: var(--_hover);
|
|
26
|
+
color: var(--_icon-active);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.qv-button:active {
|
|
30
|
+
background: var(--_active);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.qv-button:focus-visible {
|
|
34
|
+
outline: 2px solid var(--_focus);
|
|
35
|
+
outline-offset: 1px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* An invoker whose popover is open reads as held down. Stated over any
|
|
39
|
+
popover rather than over one module's menu: the platform tracks the state,
|
|
40
|
+
and this is a fact about the button, not about what it opened. */
|
|
41
|
+
.qv-button:has(+ [popover]:popover-open) {
|
|
42
|
+
background: var(--_active);
|
|
43
|
+
color: var(--_icon-active);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.qv-button:disabled {
|
|
47
|
+
background: transparent;
|
|
48
|
+
color: var(--_icon);
|
|
49
|
+
opacity: 0.4;
|
|
50
|
+
cursor: default;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
/** @type {<T, U>(value: T, otherwise: U) => T | U} */
|
|
55
|
+
let fallback = (value, otherwise) => value ?? otherwise;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A chrome button: the class, the `type` that keeps it from submitting a form
|
|
59
|
+
* the viewer may be embedded in, and the title/label pairing every control
|
|
60
|
+
* carries. Labels and content go in as template values, never as markup, so
|
|
61
|
+
* the controls have no markup edge to escape at (hard constraint 4).
|
|
62
|
+
*
|
|
63
|
+
* @param {{ title: string, label?: string, disabled?: boolean, name?: string,
|
|
64
|
+
* popover?: string, click?: () => void,
|
|
65
|
+
* content?: import('lit').TemplateResult }} control
|
|
66
|
+
* `label` is visible text for the controls that read as words; `content`
|
|
67
|
+
* is the icon template the zoom trigger and the export buttons pass
|
|
68
|
+
* instead. `name` marks an export button with the target it downloads, and
|
|
69
|
+
* `popover` names the menu this button opens — the platform then owns the
|
|
70
|
+
* opening and the invoker's own `aria-expanded`, which is why such a button
|
|
71
|
+
* needs no click of its own.
|
|
72
|
+
* @returns {import('lit').TemplateResult}
|
|
73
|
+
*/
|
|
74
|
+
export let button = ({ title, label, disabled, name, popover, click, content }) => html`
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
class="qv-button"
|
|
78
|
+
title=${title}
|
|
79
|
+
aria-label=${title}
|
|
80
|
+
aria-haspopup=${popover ? "menu" : nothing}
|
|
81
|
+
popovertarget=${fallback(popover, nothing)}
|
|
82
|
+
data-export=${fallback(name, nothing)}
|
|
83
|
+
?disabled=${disabled}
|
|
84
|
+
@click=${click}
|
|
85
|
+
>
|
|
86
|
+
${fallback(label, content)}
|
|
87
|
+
</button>
|
|
88
|
+
`;
|