@thebase/ui 1.1.0 → 1.1.2
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 +11 -0
- package/README.md +18 -4
- package/bin/create-baseui-app.mjs +450 -3
- package/dist/baseui.analytics.esm.js +1 -1
- package/dist/baseui.analytics.min.js +1 -1
- package/dist/baseui.esm.js +1 -1
- package/dist/baseui.min.js +1 -1
- package/dist/baseui.pwa.esm.js +53 -0
- package/dist/baseui.registry.json +4 -4
- package/dist/types/analytics.d.ts +11 -0
- package/dist/types/pwa.d.ts +37 -0
- package/docs/analytics.md +192 -2
- package/docs/blocks.md +1 -1
- package/docs/icons.md +1 -1
- package/docs/installation.md +2 -2
- package/docs/theming.md +1 -1
- package/docs/usage-guide.md +40 -4
- package/package.json +6 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
var u=[{src:"/icons/icon-192.png",sizes:"192x192",type:"image/png",purpose:"any"},{src:"/icons/icon-512.png",sizes:"512x512",type:"image/png",purpose:"any"},{src:"/icons/icon-512.png",sizes:"512x512",type:"image/png",purpose:"maskable"}],f=["/","/manifest.webmanifest","/favicon.svg"];function d(n){let{name:t,shortName:s=t,description:c="",startUrl:r="/",scope:e="/",display:a="standalone",orientation:i="portrait-primary",backgroundColor:o="#ffffff",themeColor:h="#0f172a",lang:l="en",icons:p=u}=n;return{name:t,short_name:s,description:c,start_url:r,scope:e,display:a,orientation:i,background_color:o,theme_color:h,lang:l,icons:p}}function m(n){let{cacheName:t,cdnVersion:s,appFiles:c=[],extraPrecacheUrls:r=[]}=n,e=`https://cdn.jsdelivr.net/npm/@thebase/ui@${s}/dist/`,a=[...f,...c,...r,`${e}baseui.min.css`,`${e}baseui.esm.js`,`${e}baseui.templates.xml`];return`const CACHE_NAME = ${JSON.stringify(t)};
|
|
2
|
+
const BASEUI_CDN = ${JSON.stringify(e)};
|
|
3
|
+
|
|
4
|
+
const PRECACHE_URLS = ${JSON.stringify(a,null,2)};
|
|
5
|
+
|
|
6
|
+
self.addEventListener("install", (event) => {
|
|
7
|
+
event.waitUntil(
|
|
8
|
+
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)).then(() => self.skipWaiting())
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
self.addEventListener("activate", (event) => {
|
|
13
|
+
event.waitUntil(
|
|
14
|
+
caches
|
|
15
|
+
.keys()
|
|
16
|
+
.then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))))
|
|
17
|
+
.then(() => self.clients.claim())
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function fromCdnCache(request) {
|
|
22
|
+
const cache = await caches.open(CACHE_NAME);
|
|
23
|
+
const cached = await cache.match(request);
|
|
24
|
+
if (cached) return cached;
|
|
25
|
+
const response = await fetch(request);
|
|
26
|
+
if (response.ok) cache.put(request, response.clone());
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
self.addEventListener("fetch", (event) => {
|
|
31
|
+
const { request } = event;
|
|
32
|
+
if (request.method !== "GET") return;
|
|
33
|
+
if (request.url.startsWith(BASEUI_CDN)) {
|
|
34
|
+
event.respondWith(fromCdnCache(request));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (new URL(request.url).origin !== self.location.origin) return;
|
|
38
|
+
|
|
39
|
+
event.respondWith(
|
|
40
|
+
caches.open(CACHE_NAME).then(async (cache) => {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(request);
|
|
43
|
+
if (response.ok) cache.put(request, response.clone());
|
|
44
|
+
return response;
|
|
45
|
+
} catch {
|
|
46
|
+
const cached = await cache.match(request);
|
|
47
|
+
if (cached) return cached;
|
|
48
|
+
throw new Error(\`No cached response for \${request.url} and network unavailable\`);
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
`}export{d as buildManifest,m as buildServiceWorker};
|
|
@@ -1662,13 +1662,13 @@
|
|
|
1662
1662
|
"integrity": {
|
|
1663
1663
|
"baseui.min.css": "sha384-hYFwlV6T/ONYzawtDGDFUoruqgNwYl9AVvLDHx24IWgKSMXqwACo/nX04I3gnyT9",
|
|
1664
1664
|
"bootstrap.bundle.min.js": "sha384-RMbllTJcplkCRnELNTOc5Kz/3AvFDE/rNlJqWqfgmRWruobClBdfwvl10akQZkR1",
|
|
1665
|
-
"baseui.min.js": "sha384-
|
|
1666
|
-
"baseui.esm.js": "sha384-
|
|
1665
|
+
"baseui.min.js": "sha384-Qv1lpDLXQ3+gI0MGXGSdeOmq7tX+Hn6/TcsSsnWikLXQhi7BTAIKNWPUeIXP1LxF",
|
|
1666
|
+
"baseui.esm.js": "sha384-IkUDqUsC56aiRgXggGe2XGZM2chM148CB837Gh/mWPtjqUEq5qH9ve5eG5texJPT",
|
|
1667
1667
|
"baseui.icons.esm.js": "sha384-hj39VMhzcFvmYnjob2hlioMOAqwAXCURGreF6s9RitXFp/iYi7rUdqtfCI+ICQeS",
|
|
1668
1668
|
"baseui.templates.xml": "sha384-XkqmfurKcEF6WuIQHWAbnZfW6oz19n7SDVRDU3g0hc3qOr4kPd/qfNGcTmcDDIgQ",
|
|
1669
1669
|
"baseui.block-viewer.esm.js": "sha384-XlIb3jDxt6Q75dbC4rLctTdPU6u0vJFcbxp9Gl6VKOkhjpz3wRBe0E3dWd80425L",
|
|
1670
|
-
"baseui.analytics.esm.js": "sha384-
|
|
1671
|
-
"baseui.analytics.min.js": "sha384-
|
|
1670
|
+
"baseui.analytics.esm.js": "sha384-M6TVmchmceTvpSzgI+DNA6zAn9xmz9a+YEexCLslSo/Ngxyp2kmzs9fm+PxWy+Da",
|
|
1671
|
+
"baseui.analytics.min.js": "sha384-B0hBkVDbT7gmDmUMazlN4Nc1KnYGSpL+IcW2hd8840knx73HGImPgGs84MvS3hKE",
|
|
1672
1672
|
"icons/lucide.json": "sha384-mc5mzpmq9eOgZVa2IcjrL33Ty4nMXE8aoZMfy+lgkC/jMQ4aYnk2S58mFsSGFnW1",
|
|
1673
1673
|
"themes/baseui-light.css": "sha384-/TosvycgVJ5aZ7EOWd4cr8Zen4nayAC/iA9BgGdXKH0LuHSnZk4guDaWM5WPC7yy",
|
|
1674
1674
|
"themes/baseui-dark.css": "sha384-zF9Rhfjpv90sxuJ2WK7GBPCltN6mkebzK1w9EzQGY9DYHiX3eIX/fcE7fXzfIZVF"
|
|
@@ -6,6 +6,16 @@ export interface BaseUIAnalytics {
|
|
|
6
6
|
track(eventName: string, options?: BaseUIAnalyticsEventOptions): void;
|
|
7
7
|
/** File types tracked by default when `fileDownloads` is enabled. */
|
|
8
8
|
DEFAULT_FILE_TYPES: string[];
|
|
9
|
+
/** Coarse visitor setup (timezone, language, screen bucket); pass as `customProperties`. */
|
|
10
|
+
visitorContext(): BaseUIVisitorContext;
|
|
11
|
+
}
|
|
12
|
+
export interface BaseUIVisitorContext {
|
|
13
|
+
/** IANA timezone, e.g. "Europe/Paris" (browsers may report legacy aliases like "Asia/Saigon"). */
|
|
14
|
+
tz: string;
|
|
15
|
+
/** `navigator.language`, e.g. "vi-VN". */
|
|
16
|
+
lang: string;
|
|
17
|
+
/** Viewport width bucket: < 576, < 992, < 1440, otherwise Desktop. */
|
|
18
|
+
screen: "Mobile" | "Tablet" | "Laptop" | "Desktop";
|
|
9
19
|
}
|
|
10
20
|
|
|
11
21
|
export interface BaseUIAnalyticsConfig {
|
|
@@ -63,3 +73,4 @@ export type BaseUIAnalyticsPayload = {
|
|
|
63
73
|
export const DEFAULT_FILE_TYPES: string[];
|
|
64
74
|
export function init(config: BaseUIAnalyticsConfig): void;
|
|
65
75
|
export function track(eventName: string, options?: BaseUIAnalyticsEventOptions): void;
|
|
76
|
+
export function visitorContext(): BaseUIVisitorContext;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Optional PWA toolkit: builds manifest.webmanifest/sw.js content from a config object. */
|
|
2
|
+
export interface BaseUIManifestConfig {
|
|
3
|
+
name: string;
|
|
4
|
+
/** Defaults to `name`. */
|
|
5
|
+
shortName?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
/** Defaults to "/". */
|
|
8
|
+
startUrl?: string;
|
|
9
|
+
/** Defaults to "/". */
|
|
10
|
+
scope?: string;
|
|
11
|
+
/** Defaults to "standalone". */
|
|
12
|
+
display?: string;
|
|
13
|
+
/** Defaults to "portrait-primary". */
|
|
14
|
+
orientation?: string;
|
|
15
|
+
/** Defaults to "#ffffff". */
|
|
16
|
+
backgroundColor?: string;
|
|
17
|
+
/** Defaults to "#0f172a". */
|
|
18
|
+
themeColor?: string;
|
|
19
|
+
/** Defaults to "en". */
|
|
20
|
+
lang?: string;
|
|
21
|
+
/** Defaults to a 192/512 `/icons/icon-*.png` set. */
|
|
22
|
+
icons?: Array<{ src: string; sizes: string; type: string; purpose?: string }>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface BaseUIServiceWorkerConfig {
|
|
26
|
+
/** Cache bucket name, e.g. "my-app-cache-v1" — bump the suffix to invalidate old caches. */
|
|
27
|
+
cacheName: string;
|
|
28
|
+
/** Pinned `@thebase/ui` version the generated sw.js precaches/serves from jsdelivr. */
|
|
29
|
+
cdnVersion: string;
|
|
30
|
+
/** Same-origin app-shell paths to precache (JS/CSS/XML/icons/etc). */
|
|
31
|
+
appFiles?: string[];
|
|
32
|
+
/** Extra same-origin or cross-origin URLs to precache alongside `appFiles`. */
|
|
33
|
+
extraPrecacheUrls?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildManifest(config: BaseUIManifestConfig): Record<string, unknown>;
|
|
37
|
+
export function buildServiceWorker(config: BaseUIServiceWorkerConfig): string;
|
package/docs/analytics.md
CHANGED
|
@@ -13,12 +13,12 @@ init({ backendUrl: "https://stats.example.com" });
|
|
|
13
13
|
```html
|
|
14
14
|
<!-- ES module from the CDN (or map it as "@base/analytics" in an import map) -->
|
|
15
15
|
<script type="module">
|
|
16
|
-
import { init } from "https://cdn.jsdelivr.net/npm/@thebase/ui@1.1.
|
|
16
|
+
import { init } from "https://cdn.jsdelivr.net/npm/@thebase/ui@1.1.1/dist/baseui.analytics.esm.js";
|
|
17
17
|
init({ backendUrl: "https://stats.example.com" });
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<!-- Classic script tag: sets window.thebase.analytics (load order vs dist/baseui.min.js doesn't matter) -->
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/@thebase/ui@1.1.
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/@thebase/ui@1.1.1/dist/baseui.analytics.min.js"></script>
|
|
22
22
|
<script>
|
|
23
23
|
thebase.analytics.init({ backendUrl: "https://stats.example.com" });
|
|
24
24
|
</script>
|
|
@@ -68,6 +68,24 @@ Or tag markup with classes — no JavaScript needed:
|
|
|
68
68
|
|
|
69
69
|
`+` in a class value becomes a space; `thebase-revenue-amount=10 thebase-revenue-currency=USD` attaches revenue.
|
|
70
70
|
|
|
71
|
+
## Visitor context
|
|
72
|
+
|
|
73
|
+
`visitorContext()` returns coarse facts about the visitor's setup. Pass it as `customProperties` to attach them to every event (available from 1.1.1):
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
import { init, visitorContext } from "@thebase/ui/analytics";
|
|
77
|
+
init({ backendUrl: "https://stats.example.com", customProperties: visitorContext });
|
|
78
|
+
// every event's props now include { tz: "Asia/Ho_Chi_Minh", lang: "vi-VN", screen: "Mobile" }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Prop | Value |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `tz` | `Intl.DateTimeFormat().resolvedOptions().timeZone`. Some browsers report legacy aliases (Chromium sends `Asia/Saigon`), so backends should canonicalize. |
|
|
84
|
+
| `lang` | `navigator.language`, e.g. `vi-VN`. |
|
|
85
|
+
| `screen` | Viewport width bucket: `Mobile` (< 576 px), `Tablet` (< 992), `Laptop` (< 1440), `Desktop`. |
|
|
86
|
+
|
|
87
|
+
The values are deliberately coarse: no exact pixels, nothing persistent. Backends can treat `tz`, `lang` and `screen` as visit dimensions instead of event props. To combine it with your own props, call it inside your function: `customProperties: (name) => ({ ...visitorContext(), plan: "pro" })`. Don't use `tz`, `lang` or `screen` as your own event prop names: event props override custom properties with the same key, so the visitor's value would be lost. Use something like `uiLang` instead. The IP address and user agent never appear in the payload; the server reads them from the HTTP request (see [Receiving events](#receiving-events-backend)).
|
|
88
|
+
|
|
71
89
|
## Engagement
|
|
72
90
|
|
|
73
91
|
After each pageview the service measures time on screen and maximum scroll depth, and sends an `engagement` event (`e` = ms, `sd` = %) when the tab is hidden, loses focus, or navigates — only if the visitor scrolled further or spent at least 3 seconds since the last report.
|
|
@@ -75,3 +93,175 @@ After each pageview the service measures time on screen and maximum scroll depth
|
|
|
75
93
|
## Ignored visits
|
|
76
94
|
|
|
77
95
|
Events are dropped (and `callback()` gets `undefined`) on localhost unless `captureOnLocalhost` is set, in automated browsers (`navigator.webdriver`, Cypress, PhantomJS, Nightmare — unless `window.__thebase` is set), or when `localStorage.thebase_ignore === "true"` (set that on your own devices to exclude them).
|
|
96
|
+
|
|
97
|
+
## Receiving events (backend)
|
|
98
|
+
|
|
99
|
+
Any HTTP server can collect events. It needs one route — `POST <backendUrl><apiPath>` (default `/api/event`) — that:
|
|
100
|
+
|
|
101
|
+
1. Reads the **raw body** as JSON. The tracker sends `Content-Type: text/plain` to avoid a CORS preflight, so framework helpers that only parse `application/json` bodies won't decode it.
|
|
102
|
+
2. Rejects bodies missing `n`, `u` or `d`, and caps the body size (events are well under 1 KB).
|
|
103
|
+
3. Answers quickly (`202 Accepted`, no body) with `Access-Control-Allow-Origin` for your site. Store or queue the event after you reply, or send it to a queue, so the visitor's browser never waits on the database.
|
|
104
|
+
|
|
105
|
+
### Payload
|
|
106
|
+
|
|
107
|
+
| Key | Type | Meaning |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| `n` | string | Event name: `pageview`, `engagement`, or a custom name (`Signup`, `Outbound Link: Click`, `File Download`, `Form: Submission`, …). |
|
|
110
|
+
| `u` | string | Page URL. |
|
|
111
|
+
| `d` | string | Site `domain`. |
|
|
112
|
+
| `r` | string \| null | `document.referrer` (pageviews and custom events). |
|
|
113
|
+
| `p` | object | Custom properties (`props` + `customProperties`); values are usually strings. |
|
|
114
|
+
| `$` | object | Revenue, e.g. `{ "amount": 10, "currency": "USD" }` (`amount` is a string when it comes from a class tag). |
|
|
115
|
+
| `i` | `false` | Present only for non-interactive events (excluded from bounce rate). |
|
|
116
|
+
| `h` | `1` | Present only with `hashBasedRouting`. |
|
|
117
|
+
| `sd` | number | `engagement` only: max scroll depth, %. |
|
|
118
|
+
| `e` | number | `engagement` only: engaged time, ms. |
|
|
119
|
+
| `v` | number | Tracker version (currently `36`). |
|
|
120
|
+
|
|
121
|
+
The payload carries no visitor ID or cookie. To count unique visitors, derive one on the server, e.g. `sha256(dailySalt + domain + clientIP + userAgent)`. Rotate the salt every day and never store the raw IP. If the server sits behind a proxy or load balancer, configure it to trust the proxy's `X-Forwarded-For` header so it sees the visitor's IP instead of the proxy's.
|
|
122
|
+
|
|
123
|
+
### Go — Fiber v3
|
|
124
|
+
|
|
125
|
+
Requires Go 1.25+ and `go get github.com/gofiber/fiber/v3` (tested with v3.5.0).
|
|
126
|
+
|
|
127
|
+
```go
|
|
128
|
+
package main
|
|
129
|
+
|
|
130
|
+
import (
|
|
131
|
+
"encoding/json"
|
|
132
|
+
"log"
|
|
133
|
+
|
|
134
|
+
"github.com/gofiber/fiber/v3"
|
|
135
|
+
"github.com/gofiber/fiber/v3/middleware/cors"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// Event is the compact payload POSTed by @thebase/ui/analytics.
|
|
139
|
+
type Event struct {
|
|
140
|
+
Name string `json:"n"`
|
|
141
|
+
URL string `json:"u"`
|
|
142
|
+
Domain string `json:"d"`
|
|
143
|
+
Referrer *string `json:"r"`
|
|
144
|
+
Props map[string]any `json:"p"`
|
|
145
|
+
Revenue map[string]any `json:"$"`
|
|
146
|
+
Interactive *bool `json:"i"`
|
|
147
|
+
HashRouting int `json:"h"`
|
|
148
|
+
ScrollDepth *int `json:"sd"`
|
|
149
|
+
EngagedMs *int64 `json:"e"`
|
|
150
|
+
Version int `json:"v"`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func main() {
|
|
154
|
+
app := fiber.New(fiber.Config{BodyLimit: 16 * 1024})
|
|
155
|
+
app.Use(cors.New(cors.Config{AllowOrigins: []string{"https://www.example.com"}}))
|
|
156
|
+
|
|
157
|
+
app.Post("/api/event", func(c fiber.Ctx) error {
|
|
158
|
+
// Body is text/plain, so decode it directly instead of c.Bind().JSON().
|
|
159
|
+
var ev Event
|
|
160
|
+
if err := json.Unmarshal(c.Body(), &ev); err != nil || ev.Name == "" || ev.URL == "" || ev.Domain == "" {
|
|
161
|
+
return c.SendStatus(fiber.StatusBadRequest)
|
|
162
|
+
}
|
|
163
|
+
log.Printf("%s %s %s ip=%s ua=%q", ev.Domain, ev.Name, ev.URL, c.IP(), c.Get(fiber.HeaderUserAgent))
|
|
164
|
+
// TODO: enqueue/store ev.
|
|
165
|
+
return c.SendStatus(fiber.StatusAccepted)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
log.Fatal(app.Listen(":8080"))
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Python — FastAPI
|
|
173
|
+
|
|
174
|
+
Requires Python 3.10+ and `pip install "fastapi[standard]"` (tested with FastAPI 0.142.2 / Pydantic 2.13). Run with `fastapi run main.py`.
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from typing import Any
|
|
178
|
+
|
|
179
|
+
from fastapi import FastAPI, Request, Response
|
|
180
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
181
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
182
|
+
|
|
183
|
+
app = FastAPI()
|
|
184
|
+
app.add_middleware(CORSMiddleware, allow_origins=["https://www.example.com"], allow_methods=["POST"])
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class Event(BaseModel):
|
|
188
|
+
"""Compact payload POSTed by @thebase/ui/analytics."""
|
|
189
|
+
|
|
190
|
+
name: str = Field(alias="n", min_length=1)
|
|
191
|
+
url: str = Field(alias="u", min_length=1)
|
|
192
|
+
domain: str = Field(alias="d", min_length=1)
|
|
193
|
+
referrer: str | None = Field(None, alias="r")
|
|
194
|
+
props: dict[str, Any] | None = Field(None, alias="p")
|
|
195
|
+
revenue: dict[str, Any] | None = Field(None, alias="$")
|
|
196
|
+
interactive: bool | None = Field(None, alias="i")
|
|
197
|
+
hash_routing: int | None = Field(None, alias="h")
|
|
198
|
+
scroll_depth: int | None = Field(None, alias="sd")
|
|
199
|
+
engaged_ms: int | None = Field(None, alias="e")
|
|
200
|
+
version: int = Field(alias="v")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@app.post("/api/event", status_code=202)
|
|
204
|
+
async def collect(request: Request) -> Response:
|
|
205
|
+
# Body is text/plain, so parse the raw bytes instead of declaring `event: Event` as a parameter.
|
|
206
|
+
body = await request.body()
|
|
207
|
+
if len(body) > 16 * 1024:
|
|
208
|
+
return Response(status_code=413)
|
|
209
|
+
try:
|
|
210
|
+
event = Event.model_validate_json(body)
|
|
211
|
+
except ValidationError:
|
|
212
|
+
return Response(status_code=400)
|
|
213
|
+
ip = request.client.host if request.client else None
|
|
214
|
+
print(event.domain, event.name, event.url, ip, request.headers.get("user-agent"))
|
|
215
|
+
# TODO: enqueue/store event.
|
|
216
|
+
return Response(status_code=202)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Point the tracker at either server with `init({ backendUrl: "https://stats.example.com" })`. To smoke-test the server, run:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
curl -i -X POST http://localhost:8080/api/event -H 'Content-Type: text/plain' \
|
|
223
|
+
-d '{"n":"pageview","v":36,"u":"https://www.example.com/","d":"www.example.com","r":null}'
|
|
224
|
+
# HTTP/1.1 202 Accepted
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Sample dashboard
|
|
228
|
+
|
|
229
|
+
[`examples/analytics/`](../examples/analytics/dashboard/?backend=http://localhost:8080) contains a working dashboard plus two complete sample backends: [`server-go`](../examples/analytics/server-go/main.go) (Fiber v3) and [`server-fastapi`](../examples/analytics/server-fastapi/main.py). Each one extends the receivers above with an in-memory store, a daily-salted cookieless visitor hash, a `GET /api/stats` endpoint, and static hosting for the dashboard.
|
|
230
|
+
|
|
231
|
+
The dashboard ([`dashboard/`](../examples/analytics/dashboard/?backend=http://localhost:8080)) is a pure Owl app that loads `@thebase/ui@1.1.1` from jsDelivr: KPI `Card`s, an area `Chart`, top pages/sources/events `Table`s, site and period `NativeSelect`s, and a `ThemeButton`. It refreshes every 30 s and tracks its own visits through `@thebase/ui/analytics`, so a fresh backend has data straight away.
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
# Go (1.25+)
|
|
235
|
+
cd examples/analytics/server-go && DEMO_SEED=1 go run .
|
|
236
|
+
|
|
237
|
+
# or Python (3.10+)
|
|
238
|
+
cd examples/analytics/server-fastapi && pip install -r requirements.txt && DEMO_SEED=1 fastapi run main.py --port 8080
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Open <http://localhost:8080/>, the backend serves the dashboard itself. If you are browsing these docs from `npm run dev` (port 5173), the [dashboard link](../examples/analytics/dashboard/?backend=http://localhost:8080) also works once a backend is running on port 8080; until then it shows a "Can't load stats" message. `DEMO_SEED=1` preloads 30 days of synthetic traffic for `demo.example.com`. Other environment variables:
|
|
242
|
+
- `ADDR`: the Go listen address.
|
|
243
|
+
- `ALLOWED_ORIGINS`: a comma-separated CORS allow-list (default `*`).
|
|
244
|
+
- `DASHBOARD_DIR`: the folder served at `/`.
|
|
245
|
+
|
|
246
|
+
To serve the dashboard from somewhere else, open it with `?backend=https://stats.example.com`.
|
|
247
|
+
|
|
248
|
+
`GET /api/stats?domain=<site>&days=<1..90>` (default: the busiest site, 7 days, UTC) returns:
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"domain": "demo.example.com",
|
|
253
|
+
"domains": ["demo.example.com", "www.example.com"],
|
|
254
|
+
"days": 7,
|
|
255
|
+
"totals": { "visitors": 643, "pageviews": 1582, "events": 124, "avgTimeSec": 62.7, "avgScrollDepth": 63.4 },
|
|
256
|
+
"timeseries": [{ "date": "2026-09-28", "visitors": 105, "pageviews": 261 }],
|
|
257
|
+
"topPages": [{ "name": "/pricing", "visitors": 296, "count": 373 }],
|
|
258
|
+
"topSources": [{ "name": "google.com", "visitors": 186, "count": 186 }],
|
|
259
|
+
"topEvents": [{ "name": "Signup", "visitors": 40, "count": 40 }]
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The sample servers are demos, not production stores:
|
|
264
|
+
- Events live in memory only and are lost on restart.
|
|
265
|
+
- `/api/stats` has no authentication.
|
|
266
|
+
|
|
267
|
+
Before going live, persist events (e.g. PostgreSQL or ClickHouse), put `/api/stats` behind auth, and restrict `ALLOWED_ORIGINS`.
|
package/docs/blocks.md
CHANGED
|
@@ -13,7 +13,7 @@ Blocks are not standalone runtime components. Paste the markup and load the norm
|
|
|
13
13
|
<script src="https://cdn.jsdelivr.net/npm/@thebase/ui@latest/dist/baseui.min.js" defer></script>
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.
|
|
16
|
+
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) instead of `@latest` for production. Blocks are static markup, so they only need the static `b-ui` runtime above — see [Usage Guide](usage-guide.md) if the surrounding page is itself an Owl app and you'd rather compose the equivalent layout from [Pure Owl Components](owl-components.md).
|
|
17
17
|
|
|
18
18
|
Use `b-block` to select the block style:
|
|
19
19
|
|
package/docs/icons.md
CHANGED
|
@@ -22,7 +22,7 @@ Load the normal BaseUI assets. No separate icon script is required.
|
|
|
22
22
|
<script src="https://cdn.jsdelivr.net/npm/@thebase/ui@latest/dist/baseui.min.js" defer></script>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.
|
|
25
|
+
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) instead of `@latest` for production. `b-icon` also works with the [pure Owl `Icon` component](components/icon.md) — the recommended API in an Owl app; the classic script above is for pages using the static `b-ui` API.
|
|
26
26
|
|
|
27
27
|
The build publishes every Lucide SVG to `dist/icons/lucide/` and writes the generated icon manifest to `dist/icons/lucide.json`.
|
|
28
28
|
|
package/docs/installation.md
CHANGED
|
@@ -4,7 +4,7 @@ See the [Usage Guide](usage-guide.md) for the end-to-end integration path (choos
|
|
|
4
4
|
|
|
5
5
|
BaseUI is distributed as one CSS file and one JavaScript file for browser use. `dist/baseui.min.js` is fully self-contained: Bootstrap's JS bundle (Popper.js included) and the Owl runtime are embedded ahead of BaseUI's own code, in load order (Bootstrap, then Owl, then BaseUI) — nothing else needs to load first. Both install paths below load this same file; the pure Owl component API is the recommended way to consume BaseUI in an Owl app, with the static `b-ui` markup API available as a lighter-weight alternative for non-Owl pages.
|
|
6
6
|
|
|
7
|
-
The snippets below use the `@latest` tag for readability. Pin an exact version instead (e.g. `@1.1.
|
|
7
|
+
The snippets below use the `@latest` tag for readability. Pin an exact version instead (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) for any production page — `@latest` can silently change what your page loads the moment a new version is published.
|
|
8
8
|
|
|
9
9
|
## Pure Owl component install (recommended)
|
|
10
10
|
|
|
@@ -43,7 +43,7 @@ For static HTML, a server-rendered page, or a Base HUB website page that has no
|
|
|
43
43
|
|
|
44
44
|
Bootstrap 5 is the bundled baseline CSS framework, and its JS bundle now drives BaseUI's interactive components directly.
|
|
45
45
|
|
|
46
|
-
`@thebase/ui` is published on npm, so [unpkg](https://unpkg.com/@thebase/ui@latest/) mirrors the same files as an alternative CDN (`https://unpkg.com/@thebase/ui@latest/dist/baseui.min.css` / `baseui.min.js`) if jsDelivr is unreachable in your environment. Pin an exact version (e.g. `@1.1.
|
|
46
|
+
`@thebase/ui` is published on npm, so [unpkg](https://unpkg.com/@thebase/ui@latest/) mirrors the same files as an alternative CDN (`https://unpkg.com/@thebase/ui@latest/dist/baseui.min.css` / `baseui.min.js`) if jsDelivr is unreachable in your environment. Pin an exact version (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) instead of `@latest` for production.
|
|
47
47
|
|
|
48
48
|
For npm/self-hosted usage:
|
|
49
49
|
|
package/docs/theming.md
CHANGED
|
@@ -38,7 +38,7 @@ For a reusable theme toggle with persistence, URL override, button-label sync, a
|
|
|
38
38
|
</script>
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
`https://unpkg.com/@thebase/ui@latest/dist/baseui.esm.js` mirrors the same file as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.
|
|
41
|
+
`https://unpkg.com/@thebase/ui@latest/dist/baseui.esm.js` mirrors the same file as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) instead of `@latest` for production. Npm/bundler consumers can import the same API from `@thebase/ui`.
|
|
42
42
|
|
|
43
43
|
For a ready-made icon button, use the BaseUI component instead of wiring your own click handler:
|
|
44
44
|
|
package/docs/usage-guide.md
CHANGED
|
@@ -32,7 +32,7 @@ Save this as an `.html` file and open it — no build step, no server required.
|
|
|
32
32
|
</html>
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
`https://cdn.jsdelivr.net/npm/@thebase/ui@latest/dist/...` is the real jsDelivr URL for the published `@thebase/ui` npm package's `latest` tag, used here for a copy-paste-and-go quick start. Pin an exact version instead (e.g. `@1.1.
|
|
35
|
+
`https://cdn.jsdelivr.net/npm/@thebase/ui@latest/dist/...` is the real jsDelivr URL for the published `@thebase/ui` npm package's `latest` tag, used here for a copy-paste-and-go quick start. Pin an exact version instead (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) for production, so a new release can't change what your page loads. See [Installation](installation.md) for jsDelivr/unpkg/npm/self-hosted paths.
|
|
36
36
|
|
|
37
37
|
The static bundle calls `thebase.mountAll()` automatically on `DOMContentLoaded`, so initial `[b-ui]` and `[b-icon]` markup mounts without an extra script. Call `thebase.mountAll(root)` yourself only after injecting new markup or when mounting a specific fragment. If the consumer is itself an Owl app, use [step 2](#2-install-pure-owl-api--recommended) instead — that's the recommended path.
|
|
38
38
|
|
|
@@ -63,7 +63,7 @@ Both APIs come from the same package and the same CSS file, and can be mixed on
|
|
|
63
63
|
</script>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable — swap the host in each URL above. Pin an exact version (e.g. `@1.1.
|
|
66
|
+
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable — swap the host in each URL above. Pin an exact version (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) instead of `@latest` for production.
|
|
67
67
|
|
|
68
68
|
```html
|
|
69
69
|
<script type="module">
|
|
@@ -103,7 +103,7 @@ Two rules that account for most integration failures — see [Pure Owl Component
|
|
|
103
103
|
<script src="https://cdn.jsdelivr.net/npm/@thebase/ui@latest/dist/baseui.min.js" defer></script>
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.
|
|
106
|
+
`https://unpkg.com/@thebase/ui@latest/dist/...` mirrors the same files as an alternative CDN if jsDelivr is unreachable. Pin an exact version (e.g. `@1.1.1`, or `@1` to take only non-breaking updates) instead of `@latest` for production.
|
|
107
107
|
|
|
108
108
|
**Option B — npm (bundler-based apps: Vite, webpack, etc.):**
|
|
109
109
|
|
|
@@ -120,7 +120,7 @@ import "@thebase/ui"; // side-effect import: registers window.thebase and auto-m
|
|
|
120
120
|
|
|
121
121
|
Rule that applies to both options:
|
|
122
122
|
|
|
123
|
-
- Production pages must use a pinned version (e.g. `@thebase/ui@1.1.
|
|
123
|
+
- Production pages must use a pinned version (e.g. `@thebase/ui@1.1.1` or `@thebase/ui@1`), never `@latest`.
|
|
124
124
|
|
|
125
125
|
Mount markup:
|
|
126
126
|
|
|
@@ -200,6 +200,42 @@ Full token list and the shadcn-aligned default palette: [Theming](theming.md).
|
|
|
200
200
|
|
|
201
201
|
Icons ship with the normal BaseUI assets — no separate script tag, and every Lucide icon name is available by kebab-case (`panel-left`), camelCase, or snake_case. Sizing, color, accessibility rules, and self-hosting: [Built-in Icons](icons.md).
|
|
202
202
|
|
|
203
|
+
## 5b. PWA toolkit (optional) — generate manifest.webmanifest + sw.js
|
|
204
|
+
|
|
205
|
+
Any BaseUI app can become an installable PWA without hand-writing a service worker. `@thebase/ui/pwa`
|
|
206
|
+
exports two pure functions, `buildManifest(config)` and `buildServiceWorker(config)`, that turn a
|
|
207
|
+
config object into the exact manifest JSON and service-worker source a hand-written PWA needs
|
|
208
|
+
(precache the app shell, network-first for same-origin requests, cache-first for the pinned CDN
|
|
209
|
+
build). Define the variables once, regenerate the files whenever they change:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
// baseui.pwa.config.js
|
|
213
|
+
export default {
|
|
214
|
+
manifest: {
|
|
215
|
+
name: "My App",
|
|
216
|
+
shortName: "MyApp",
|
|
217
|
+
description: "One sentence about the app.",
|
|
218
|
+
themeColor: "#0f172a",
|
|
219
|
+
},
|
|
220
|
+
serviceWorker: {
|
|
221
|
+
cacheName: "my-app-cache-v1",
|
|
222
|
+
cdnVersion: "1.1.1", // pin to match the importmap in index.html
|
|
223
|
+
appFiles: ["/app.css", "/app.js", "/app.xml", "/favicon.svg", "/icons/icon-192.png", "/icons/icon-512.png"],
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
npx create-baseui-app pwa . --config baseui.pwa.config.js
|
|
230
|
+
# writes ./manifest.webmanifest and ./sw.js
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Re-run the command whenever `appFiles`, the CDN pin, or manifest fields change — both output files are
|
|
234
|
+
fully regenerated, never hand-patched. Only `manifest.name`, `serviceWorker.cacheName`, and
|
|
235
|
+
`serviceWorker.cdnVersion` are required; everything else (icons, theme/background color, display
|
|
236
|
+
mode, extra precache URLs) has the same defaults the `qr-page` example ships with. See
|
|
237
|
+
`dist/types/pwa.d.ts` for the full config shape.
|
|
238
|
+
|
|
203
239
|
## 6. Copy a block instead of assembling from primitives
|
|
204
240
|
|
|
205
241
|
For common layouts (dashboard shells, sidebars, login/signup forms, charts), copy a ready-made block from the gallery instead of composing primitives by hand: [Blocks](blocks.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thebase/ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "CDN-installable Owl and Bootstrap 5 UI component library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/baseui.min.js",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"import": "./dist/baseui.analytics.esm.js",
|
|
26
26
|
"default": "./dist/baseui.analytics.min.js"
|
|
27
27
|
},
|
|
28
|
+
"./pwa": {
|
|
29
|
+
"types": "./dist/types/pwa.d.ts",
|
|
30
|
+
"import": "./dist/baseui.pwa.esm.js"
|
|
31
|
+
},
|
|
28
32
|
"./dist/baseui.templates.xml": "./dist/baseui.templates.xml"
|
|
29
33
|
},
|
|
30
34
|
"files": [
|
|
@@ -35,6 +39,7 @@
|
|
|
35
39
|
"dist/baseui.icons.esm.js",
|
|
36
40
|
"dist/baseui.analytics.esm.js",
|
|
37
41
|
"dist/baseui.analytics.min.js",
|
|
42
|
+
"dist/baseui.pwa.esm.js",
|
|
38
43
|
"dist/baseui.templates.xml",
|
|
39
44
|
"dist/icons",
|
|
40
45
|
"dist/baseui.registry.json",
|