@colixsystems/widget-sdk 0.283.0 → 0.285.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/README.md +9 -1
- package/dist/container-pattern.cjs +196 -0
- package/dist/container-pattern.js +191 -0
- package/dist/contract.cjs +4 -2
- package/dist/contract.js +4 -2
- package/dist/host.d.ts +30 -0
- package/dist/host.js +11 -0
- package/dist/index.d.ts +8 -0
- package/package.json +1 -1
- package/src/contract.js +4 -2
package/README.md
CHANGED
|
@@ -89,7 +89,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
89
89
|
|
|
90
90
|
## Status
|
|
91
91
|
|
|
92
|
-
`v0.
|
|
92
|
+
`v0.285.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
93
|
+
|
|
94
|
+
### What's new in 0.285.0 (contract 1.233.0)
|
|
95
|
+
|
|
96
|
+
**Host only: container background patterns.** `@colixsystems/widget-sdk/host` exports `normaliseContainerPattern`, `containerPatternTile` and `containerPatternSvg`, the one definition of a layout container's `backgroundPattern` (dots, grid, lines, stripes, grain) that the web Player and the native export both tile. Nothing changes for widget authors.
|
|
97
|
+
|
|
98
|
+
### What's new in 0.284.0 (contract 1.233.0)
|
|
99
|
+
|
|
100
|
+
**A conversation can go when its last member leaves.** A `datastoreTemplate` table may declare `deleteWhenLastMemberLeaves: true`. Use it on a table whose rows people join and leave through per-record grants, such as conversations. When a signed-in member gives up their own grant and that leaves the row with none, and no table-wide grant lets anyone read the row, the row is deleted for good, together with every row a `CASCADE` relation ties to it (its messages, reactions and read states, including ones deleted earlier). A row that never had a grant, such as a seeded sample row, is never deleted this way, and a revoke made in the Studio never deletes anything. Each deletion is recorded in the table's history. An upgrade that declares it turns it on for the table already installed. The platform is rolling this out gradually, so a left row may stay in place for now.
|
|
93
101
|
|
|
94
102
|
### What's new in 0.283.0 (contract 1.232.0)
|
|
95
103
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// GENERATED from container-pattern.js by scripts/generate-cjs.js — DO NOT EDIT.
|
|
2
|
+
// Change container-pattern.js, then run `npm run generate:cjs` in packages/widget-sdk.
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { isHexColor } = require("./contract.cjs");
|
|
6
|
+
|
|
7
|
+
// sc-14027 — a layout container's background pattern: dot paper, a grid, ruled
|
|
8
|
+
// lines, a diagonal hatch or film grain, tiled over the fill and under the
|
|
9
|
+
// scrim. The ONE definition of the vocabulary and of each tile's geometry, so
|
|
10
|
+
// the web Player (a CSS background tile), the compiled Expo app (a
|
|
11
|
+
// react-native-svg <Pattern>) and Mason's plan validator cannot disagree.
|
|
12
|
+
//
|
|
13
|
+
// Host-integration surface, re-exported from `host.js`.
|
|
14
|
+
|
|
15
|
+
const CONTAINER_PATTERN_KINDS = Object.freeze([
|
|
16
|
+
"dots",
|
|
17
|
+
"grid",
|
|
18
|
+
"lines",
|
|
19
|
+
"stripes",
|
|
20
|
+
"grain",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
// Tile edge in px (grain: the scale of its specks). Opacity is a 0-100
|
|
24
|
+
// percentage, like every other opacity a container style carries.
|
|
25
|
+
const CONTAINER_PATTERN_SIZE = Object.freeze({ min: 4, max: 64, default: 12 });
|
|
26
|
+
const CONTAINER_PATTERN_OPACITY = Object.freeze({ min: 0, max: 100, default: 15 });
|
|
27
|
+
|
|
28
|
+
const FALLBACK_COLOR = "#000000";
|
|
29
|
+
const PATTERN_KEYS = ["kind", "color", "size", "opacity"];
|
|
30
|
+
|
|
31
|
+
function clampNumber(value, { min, max, default: fallback }) {
|
|
32
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
33
|
+
const n = Number(value);
|
|
34
|
+
if (!Number.isFinite(n)) return fallback;
|
|
35
|
+
return Math.min(Math.max(n, min), max);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function inRange(value, { min, max }) {
|
|
39
|
+
return value === undefined || (typeof value === "number" && value >= min && value <= max);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Strict: what a writer (Mason) must send. The hosts clamp instead, so a
|
|
43
|
+
// value only strict mode refuses still paints.
|
|
44
|
+
function isStrictPattern(raw) {
|
|
45
|
+
return (
|
|
46
|
+
Object.keys(raw).every((key) => PATTERN_KEYS.includes(key)) &&
|
|
47
|
+
(raw.color === undefined || raw.color === "" || isHexColor(raw.color)) &&
|
|
48
|
+
inRange(raw.size, CONTAINER_PATTERN_SIZE) &&
|
|
49
|
+
inRange(raw.opacity, CONTAINER_PATTERN_OPACITY)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve an authored `style.backgroundPattern` into what a host paints.
|
|
55
|
+
*
|
|
56
|
+
* @param {unknown} raw `{ kind, color?, size?, opacity? }`
|
|
57
|
+
* @param {string} [fallbackColor] the ink an unset colour takes, usually the surface's `onSurface`
|
|
58
|
+
* @param {{strict?: boolean}} [options] strict: refuse unknown keys and out-of-range values instead of clamping
|
|
59
|
+
* @returns {{kind: string, color: string, size: number, opacity: number}|null}
|
|
60
|
+
* null when no recognised kind is set, so an unpatterned container paints nothing new.
|
|
61
|
+
* `opacity` is the 0-1 alpha.
|
|
62
|
+
*/
|
|
63
|
+
function normaliseContainerPattern(raw, fallbackColor, { strict = false } = {}) {
|
|
64
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
65
|
+
if (!CONTAINER_PATTERN_KINDS.includes(raw.kind)) return null;
|
|
66
|
+
if (strict && !isStrictPattern(raw)) return null;
|
|
67
|
+
// Hex only: the colour is written into SVG markup on the web.
|
|
68
|
+
const color = isHexColor(raw.color)
|
|
69
|
+
? raw.color
|
|
70
|
+
: isHexColor(fallbackColor)
|
|
71
|
+
? fallbackColor
|
|
72
|
+
: FALLBACK_COLOR;
|
|
73
|
+
const opacity = clampNumber(raw.opacity, CONTAINER_PATTERN_OPACITY) / 100;
|
|
74
|
+
if (opacity === 0) return null;
|
|
75
|
+
return {
|
|
76
|
+
kind: raw.kind,
|
|
77
|
+
color,
|
|
78
|
+
size: Math.round(clampNumber(raw.size, CONTAINER_PATTERN_SIZE)),
|
|
79
|
+
opacity,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
84
|
+
|
|
85
|
+
// The grain tile: a fixed speckle on a GRAIN_CELLS square grid, seeded so every
|
|
86
|
+
// host and every render draws the same noise. Not feTurbulence: native has no
|
|
87
|
+
// filter primitives, and a computed tile is identical on both hosts.
|
|
88
|
+
const GRAIN_CELLS = 48;
|
|
89
|
+
const GRAIN_LEVELS = [0.35, 0.6, 1];
|
|
90
|
+
const GRAIN_SPECKS = (() => {
|
|
91
|
+
let seed = 0x14027;
|
|
92
|
+
const next = () => {
|
|
93
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
94
|
+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
95
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
96
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
97
|
+
};
|
|
98
|
+
const byLevel = GRAIN_LEVELS.map(() => []);
|
|
99
|
+
for (let y = 0; y < GRAIN_CELLS; y += 1) {
|
|
100
|
+
for (let x = 0; x < GRAIN_CELLS; x += 1) {
|
|
101
|
+
if (next() < 0.1) byLevel[Math.floor(next() * GRAIN_LEVELS.length)].push([x, y]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return byLevel;
|
|
105
|
+
})();
|
|
106
|
+
|
|
107
|
+
function grainTile(size) {
|
|
108
|
+
const cell = size / CONTAINER_PATTERN_SIZE.default;
|
|
109
|
+
const c = round2(cell);
|
|
110
|
+
return {
|
|
111
|
+
size: round2(GRAIN_CELLS * cell),
|
|
112
|
+
paths: GRAIN_SPECKS.map((specks, i) => ({
|
|
113
|
+
d: specks.map(([x, y]) => `M${round2(x * cell)} ${round2(y * cell)}h${c}v${c}h-${c}z`).join(""),
|
|
114
|
+
fill: true,
|
|
115
|
+
alpha: GRAIN_LEVELS[i],
|
|
116
|
+
})),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Tiles depend only on kind and the clamped size, so the cache stays small.
|
|
121
|
+
const TILE_CACHE = new Map();
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The repeating tile for a resolved pattern, as plain SVG path data both hosts
|
|
125
|
+
* draw: the web as an SVG string, native as react-native-svg `<Path>`s.
|
|
126
|
+
*
|
|
127
|
+
* @param {{kind: string, size: number}} pattern output of {@link normaliseContainerPattern}
|
|
128
|
+
* @returns {{size: number, paths: Array<{d: string, fill?: boolean, strokeWidth?: number, alpha?: number}>}}
|
|
129
|
+
* shared and cached: do not mutate it.
|
|
130
|
+
*/
|
|
131
|
+
function containerPatternTile(pattern) {
|
|
132
|
+
const key = `${pattern.kind}:${pattern.size}`;
|
|
133
|
+
if (!TILE_CACHE.has(key)) TILE_CACHE.set(key, buildTile(pattern.kind, pattern.size));
|
|
134
|
+
return TILE_CACHE.get(key);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function buildTile(kind, s) {
|
|
138
|
+
if (kind === "grain") return grainTile(s);
|
|
139
|
+
if (kind === "dots") {
|
|
140
|
+
const r = round2(Math.max(1, s / 8));
|
|
141
|
+
const c = s / 2;
|
|
142
|
+
return {
|
|
143
|
+
size: s,
|
|
144
|
+
paths: [{ d: `M${round2(c - r)} ${c}a${r} ${r} 0 1 0 ${round2(r * 2)} 0a${r} ${r} 0 1 0 -${round2(r * 2)} 0z`, fill: true }],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (kind === "grid") {
|
|
148
|
+
return { size: s, paths: [{ d: `M0 0.5H${s}M0.5 0V${s}`, strokeWidth: 1 }] };
|
|
149
|
+
}
|
|
150
|
+
if (kind === "lines") {
|
|
151
|
+
return { size: s, paths: [{ d: `M0 0.5H${s}`, strokeWidth: 1 }] };
|
|
152
|
+
}
|
|
153
|
+
// stripes: a 45° hatch. The two corner segments complete the stripe the
|
|
154
|
+
// neighbouring tiles cut, so the hatch runs unbroken across tile edges.
|
|
155
|
+
const h = s / 2;
|
|
156
|
+
return {
|
|
157
|
+
size: s,
|
|
158
|
+
paths: [
|
|
159
|
+
{
|
|
160
|
+
d: `M0 ${s}L${s} 0M-${h} ${h}L${h} -${h}M${h} ${s + h}L${s + h} ${h}`,
|
|
161
|
+
strokeWidth: round2(Math.max(1, s / 4)),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The tile as standalone SVG markup, for the web host's repeating CSS
|
|
169
|
+
* background. The colour is hex-validated by {@link normaliseContainerPattern}
|
|
170
|
+
* and the path data is numeric, so nothing authored reaches the markup raw.
|
|
171
|
+
*
|
|
172
|
+
* @param {{kind: string, color: string, size: number}} pattern
|
|
173
|
+
* @returns {string}
|
|
174
|
+
*/
|
|
175
|
+
function containerPatternSvg(pattern) {
|
|
176
|
+
const tile = containerPatternTile(pattern);
|
|
177
|
+
const paths = tile.paths
|
|
178
|
+
.map((p) => {
|
|
179
|
+
const paint = p.fill
|
|
180
|
+
? `fill="${pattern.color}"`
|
|
181
|
+
: `fill="none" stroke="${pattern.color}" stroke-width="${p.strokeWidth}"`;
|
|
182
|
+
const alpha = p.alpha === undefined ? "" : ` opacity="${p.alpha}"`;
|
|
183
|
+
return `<path d="${p.d}" ${paint}${alpha}/>`;
|
|
184
|
+
})
|
|
185
|
+
.join("");
|
|
186
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${tile.size}" height="${tile.size}">${paths}</svg>`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
CONTAINER_PATTERN_KINDS,
|
|
191
|
+
CONTAINER_PATTERN_SIZE,
|
|
192
|
+
CONTAINER_PATTERN_OPACITY,
|
|
193
|
+
normaliseContainerPattern,
|
|
194
|
+
containerPatternTile,
|
|
195
|
+
containerPatternSvg,
|
|
196
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// sc-14027 — a layout container's background pattern: dot paper, a grid, ruled
|
|
2
|
+
// lines, a diagonal hatch or film grain, tiled over the fill and under the
|
|
3
|
+
// scrim. The ONE definition of the vocabulary and of each tile's geometry, so
|
|
4
|
+
// the web Player (a CSS background tile), the compiled Expo app (a
|
|
5
|
+
// react-native-svg <Pattern>) and Mason's plan validator cannot disagree.
|
|
6
|
+
//
|
|
7
|
+
// Host-integration surface, re-exported from `host.js`.
|
|
8
|
+
import { isHexColor } from "./contract.js";
|
|
9
|
+
|
|
10
|
+
const CONTAINER_PATTERN_KINDS = Object.freeze([
|
|
11
|
+
"dots",
|
|
12
|
+
"grid",
|
|
13
|
+
"lines",
|
|
14
|
+
"stripes",
|
|
15
|
+
"grain",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// Tile edge in px (grain: the scale of its specks). Opacity is a 0-100
|
|
19
|
+
// percentage, like every other opacity a container style carries.
|
|
20
|
+
const CONTAINER_PATTERN_SIZE = Object.freeze({ min: 4, max: 64, default: 12 });
|
|
21
|
+
const CONTAINER_PATTERN_OPACITY = Object.freeze({ min: 0, max: 100, default: 15 });
|
|
22
|
+
|
|
23
|
+
const FALLBACK_COLOR = "#000000";
|
|
24
|
+
const PATTERN_KEYS = ["kind", "color", "size", "opacity"];
|
|
25
|
+
|
|
26
|
+
function clampNumber(value, { min, max, default: fallback }) {
|
|
27
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
28
|
+
const n = Number(value);
|
|
29
|
+
if (!Number.isFinite(n)) return fallback;
|
|
30
|
+
return Math.min(Math.max(n, min), max);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function inRange(value, { min, max }) {
|
|
34
|
+
return value === undefined || (typeof value === "number" && value >= min && value <= max);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Strict: what a writer (Mason) must send. The hosts clamp instead, so a
|
|
38
|
+
// value only strict mode refuses still paints.
|
|
39
|
+
function isStrictPattern(raw) {
|
|
40
|
+
return (
|
|
41
|
+
Object.keys(raw).every((key) => PATTERN_KEYS.includes(key)) &&
|
|
42
|
+
(raw.color === undefined || raw.color === "" || isHexColor(raw.color)) &&
|
|
43
|
+
inRange(raw.size, CONTAINER_PATTERN_SIZE) &&
|
|
44
|
+
inRange(raw.opacity, CONTAINER_PATTERN_OPACITY)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve an authored `style.backgroundPattern` into what a host paints.
|
|
50
|
+
*
|
|
51
|
+
* @param {unknown} raw `{ kind, color?, size?, opacity? }`
|
|
52
|
+
* @param {string} [fallbackColor] the ink an unset colour takes, usually the surface's `onSurface`
|
|
53
|
+
* @param {{strict?: boolean}} [options] strict: refuse unknown keys and out-of-range values instead of clamping
|
|
54
|
+
* @returns {{kind: string, color: string, size: number, opacity: number}|null}
|
|
55
|
+
* null when no recognised kind is set, so an unpatterned container paints nothing new.
|
|
56
|
+
* `opacity` is the 0-1 alpha.
|
|
57
|
+
*/
|
|
58
|
+
function normaliseContainerPattern(raw, fallbackColor, { strict = false } = {}) {
|
|
59
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
60
|
+
if (!CONTAINER_PATTERN_KINDS.includes(raw.kind)) return null;
|
|
61
|
+
if (strict && !isStrictPattern(raw)) return null;
|
|
62
|
+
// Hex only: the colour is written into SVG markup on the web.
|
|
63
|
+
const color = isHexColor(raw.color)
|
|
64
|
+
? raw.color
|
|
65
|
+
: isHexColor(fallbackColor)
|
|
66
|
+
? fallbackColor
|
|
67
|
+
: FALLBACK_COLOR;
|
|
68
|
+
const opacity = clampNumber(raw.opacity, CONTAINER_PATTERN_OPACITY) / 100;
|
|
69
|
+
if (opacity === 0) return null;
|
|
70
|
+
return {
|
|
71
|
+
kind: raw.kind,
|
|
72
|
+
color,
|
|
73
|
+
size: Math.round(clampNumber(raw.size, CONTAINER_PATTERN_SIZE)),
|
|
74
|
+
opacity,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
79
|
+
|
|
80
|
+
// The grain tile: a fixed speckle on a GRAIN_CELLS square grid, seeded so every
|
|
81
|
+
// host and every render draws the same noise. Not feTurbulence: native has no
|
|
82
|
+
// filter primitives, and a computed tile is identical on both hosts.
|
|
83
|
+
const GRAIN_CELLS = 48;
|
|
84
|
+
const GRAIN_LEVELS = [0.35, 0.6, 1];
|
|
85
|
+
const GRAIN_SPECKS = (() => {
|
|
86
|
+
let seed = 0x14027;
|
|
87
|
+
const next = () => {
|
|
88
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
89
|
+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
90
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
91
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
92
|
+
};
|
|
93
|
+
const byLevel = GRAIN_LEVELS.map(() => []);
|
|
94
|
+
for (let y = 0; y < GRAIN_CELLS; y += 1) {
|
|
95
|
+
for (let x = 0; x < GRAIN_CELLS; x += 1) {
|
|
96
|
+
if (next() < 0.1) byLevel[Math.floor(next() * GRAIN_LEVELS.length)].push([x, y]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return byLevel;
|
|
100
|
+
})();
|
|
101
|
+
|
|
102
|
+
function grainTile(size) {
|
|
103
|
+
const cell = size / CONTAINER_PATTERN_SIZE.default;
|
|
104
|
+
const c = round2(cell);
|
|
105
|
+
return {
|
|
106
|
+
size: round2(GRAIN_CELLS * cell),
|
|
107
|
+
paths: GRAIN_SPECKS.map((specks, i) => ({
|
|
108
|
+
d: specks.map(([x, y]) => `M${round2(x * cell)} ${round2(y * cell)}h${c}v${c}h-${c}z`).join(""),
|
|
109
|
+
fill: true,
|
|
110
|
+
alpha: GRAIN_LEVELS[i],
|
|
111
|
+
})),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Tiles depend only on kind and the clamped size, so the cache stays small.
|
|
116
|
+
const TILE_CACHE = new Map();
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The repeating tile for a resolved pattern, as plain SVG path data both hosts
|
|
120
|
+
* draw: the web as an SVG string, native as react-native-svg `<Path>`s.
|
|
121
|
+
*
|
|
122
|
+
* @param {{kind: string, size: number}} pattern output of {@link normaliseContainerPattern}
|
|
123
|
+
* @returns {{size: number, paths: Array<{d: string, fill?: boolean, strokeWidth?: number, alpha?: number}>}}
|
|
124
|
+
* shared and cached: do not mutate it.
|
|
125
|
+
*/
|
|
126
|
+
function containerPatternTile(pattern) {
|
|
127
|
+
const key = `${pattern.kind}:${pattern.size}`;
|
|
128
|
+
if (!TILE_CACHE.has(key)) TILE_CACHE.set(key, buildTile(pattern.kind, pattern.size));
|
|
129
|
+
return TILE_CACHE.get(key);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function buildTile(kind, s) {
|
|
133
|
+
if (kind === "grain") return grainTile(s);
|
|
134
|
+
if (kind === "dots") {
|
|
135
|
+
const r = round2(Math.max(1, s / 8));
|
|
136
|
+
const c = s / 2;
|
|
137
|
+
return {
|
|
138
|
+
size: s,
|
|
139
|
+
paths: [{ d: `M${round2(c - r)} ${c}a${r} ${r} 0 1 0 ${round2(r * 2)} 0a${r} ${r} 0 1 0 -${round2(r * 2)} 0z`, fill: true }],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (kind === "grid") {
|
|
143
|
+
return { size: s, paths: [{ d: `M0 0.5H${s}M0.5 0V${s}`, strokeWidth: 1 }] };
|
|
144
|
+
}
|
|
145
|
+
if (kind === "lines") {
|
|
146
|
+
return { size: s, paths: [{ d: `M0 0.5H${s}`, strokeWidth: 1 }] };
|
|
147
|
+
}
|
|
148
|
+
// stripes: a 45° hatch. The two corner segments complete the stripe the
|
|
149
|
+
// neighbouring tiles cut, so the hatch runs unbroken across tile edges.
|
|
150
|
+
const h = s / 2;
|
|
151
|
+
return {
|
|
152
|
+
size: s,
|
|
153
|
+
paths: [
|
|
154
|
+
{
|
|
155
|
+
d: `M0 ${s}L${s} 0M-${h} ${h}L${h} -${h}M${h} ${s + h}L${s + h} ${h}`,
|
|
156
|
+
strokeWidth: round2(Math.max(1, s / 4)),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The tile as standalone SVG markup, for the web host's repeating CSS
|
|
164
|
+
* background. The colour is hex-validated by {@link normaliseContainerPattern}
|
|
165
|
+
* and the path data is numeric, so nothing authored reaches the markup raw.
|
|
166
|
+
*
|
|
167
|
+
* @param {{kind: string, color: string, size: number}} pattern
|
|
168
|
+
* @returns {string}
|
|
169
|
+
*/
|
|
170
|
+
function containerPatternSvg(pattern) {
|
|
171
|
+
const tile = containerPatternTile(pattern);
|
|
172
|
+
const paths = tile.paths
|
|
173
|
+
.map((p) => {
|
|
174
|
+
const paint = p.fill
|
|
175
|
+
? `fill="${pattern.color}"`
|
|
176
|
+
: `fill="none" stroke="${pattern.color}" stroke-width="${p.strokeWidth}"`;
|
|
177
|
+
const alpha = p.alpha === undefined ? "" : ` opacity="${p.alpha}"`;
|
|
178
|
+
return `<path d="${p.d}" ${paint}${alpha}/>`;
|
|
179
|
+
})
|
|
180
|
+
.join("");
|
|
181
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${tile.size}" height="${tile.size}">${paths}</svg>`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export {
|
|
185
|
+
CONTAINER_PATTERN_KINDS,
|
|
186
|
+
CONTAINER_PATTERN_SIZE,
|
|
187
|
+
CONTAINER_PATTERN_OPACITY,
|
|
188
|
+
normaliseContainerPattern,
|
|
189
|
+
containerPatternTile,
|
|
190
|
+
containerPatternSvg,
|
|
191
|
+
};
|
package/dist/contract.cjs
CHANGED
|
@@ -4151,7 +4151,7 @@ const MANIFEST_SCHEMA = {
|
|
|
4151
4151
|
type: "object",
|
|
4152
4152
|
required: false,
|
|
4153
4153
|
description:
|
|
4154
|
-
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
4154
|
+
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A table whose rows people join and leave through per-record grants (conversations) may declare `deleteWhenLastMemberLeaves: true`: when a signed-in member giving up their own grant leaves a row with no grant and no table-wide grant lets anyone read it, the row and every row a CASCADE relation ties to it (including rows deleted earlier) are deleted for good. A row that never had a grant (a seeded sample row) is never deleted this way, and a revoke made in the Studio never deletes. An upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
4155
4155
|
},
|
|
4156
4156
|
translations: {
|
|
4157
4157
|
type: "object",
|
|
@@ -6717,7 +6717,9 @@ const CONTRACT = deepFreeze({
|
|
|
6717
6717
|
// page at a time, with loadMore / hasMore / retry) and
|
|
6718
6718
|
// `pageSizeProperty`, exported as `PAGE_SIZE_PROPERTY`: the standard
|
|
6719
6719
|
// "Rows per page" property.
|
|
6720
|
-
|
|
6720
|
+
// 1.233.0: additive (sc-8115) — a `datastoreTemplate` table may declare
|
|
6721
|
+
// `deleteWhenLastMemberLeaves: true`.
|
|
6722
|
+
version: "1.233.0",
|
|
6721
6723
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6722
6724
|
hooks: HOOKS,
|
|
6723
6725
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -4147,7 +4147,7 @@ const MANIFEST_SCHEMA = {
|
|
|
4147
4147
|
type: "object",
|
|
4148
4148
|
required: false,
|
|
4149
4149
|
description:
|
|
4150
|
-
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
4150
|
+
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A table whose rows people join and leave through per-record grants (conversations) may declare `deleteWhenLastMemberLeaves: true`: when a signed-in member giving up their own grant leaves a row with no grant and no table-wide grant lets anyone read it, the row and every row a CASCADE relation ties to it (including rows deleted earlier) are deleted for good. A row that never had a grant (a seeded sample row) is never deleted this way, and a revoke made in the Studio never deletes. An upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
4151
4151
|
},
|
|
4152
4152
|
translations: {
|
|
4153
4153
|
type: "object",
|
|
@@ -6713,7 +6713,9 @@ const CONTRACT = deepFreeze({
|
|
|
6713
6713
|
// page at a time, with loadMore / hasMore / retry) and
|
|
6714
6714
|
// `pageSizeProperty`, exported as `PAGE_SIZE_PROPERTY`: the standard
|
|
6715
6715
|
// "Rows per page" property.
|
|
6716
|
-
|
|
6716
|
+
// 1.233.0: additive (sc-8115) — a `datastoreTemplate` table may declare
|
|
6717
|
+
// `deleteWhenLastMemberLeaves: true`.
|
|
6718
|
+
version: "1.233.0",
|
|
6717
6719
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6718
6720
|
hooks: HOOKS,
|
|
6719
6721
|
primitives: PRIMITIVES,
|
package/dist/host.d.ts
CHANGED
|
@@ -712,3 +712,33 @@ export const DARK_PALETTE_KEYS: readonly string[];
|
|
|
712
712
|
* appearance setting stores it: the base light, `dark` the dark palette.
|
|
713
713
|
*/
|
|
714
714
|
export function themeInBothModes<T>(theme: T, mode: AppearanceMode, twin: unknown): T;
|
|
715
|
+
|
|
716
|
+
/** A container background pattern, resolved for painting. `opacity` is 0-1. */
|
|
717
|
+
export interface ContainerPattern {
|
|
718
|
+
kind: "dots" | "grid" | "lines" | "stripes" | "grain";
|
|
719
|
+
color: string;
|
|
720
|
+
size: number;
|
|
721
|
+
opacity: number;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/** One path of a pattern tile: filled, or stroked at `strokeWidth`. */
|
|
725
|
+
export interface ContainerPatternPath {
|
|
726
|
+
d: string;
|
|
727
|
+
fill?: boolean;
|
|
728
|
+
strokeWidth?: number;
|
|
729
|
+
alpha?: number;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
export const CONTAINER_PATTERN_KINDS: readonly ContainerPattern["kind"][];
|
|
733
|
+
export const CONTAINER_PATTERN_SIZE: { readonly min: number; readonly max: number; readonly default: number };
|
|
734
|
+
export const CONTAINER_PATTERN_OPACITY: { readonly min: number; readonly max: number; readonly default: number };
|
|
735
|
+
/** A container's `style.backgroundPattern`, or null when it names no known kind. */
|
|
736
|
+
export function normaliseContainerPattern(
|
|
737
|
+
raw: unknown,
|
|
738
|
+
fallbackColor?: string,
|
|
739
|
+
options?: { strict?: boolean },
|
|
740
|
+
): ContainerPattern | null;
|
|
741
|
+
/** The repeating tile both hosts draw. */
|
|
742
|
+
export function containerPatternTile(pattern: ContainerPattern): { size: number; paths: ContainerPatternPath[] };
|
|
743
|
+
/** The tile as standalone SVG markup, for a CSS background. */
|
|
744
|
+
export function containerPatternSvg(pattern: ContainerPattern): string;
|
package/dist/host.js
CHANGED
|
@@ -173,3 +173,14 @@ export {
|
|
|
173
173
|
dateTimeLocalInputToIso,
|
|
174
174
|
formatDisplayLabel as formatDateTimeLabel,
|
|
175
175
|
} from "./datetimepicker-format.js";
|
|
176
|
+
|
|
177
|
+
// sc-14027: a container's background pattern — vocabulary and tile geometry,
|
|
178
|
+
// so the Player's CSS tile and the export's <Pattern> draw the same thing.
|
|
179
|
+
export {
|
|
180
|
+
CONTAINER_PATTERN_KINDS,
|
|
181
|
+
CONTAINER_PATTERN_SIZE,
|
|
182
|
+
CONTAINER_PATTERN_OPACITY,
|
|
183
|
+
normaliseContainerPattern,
|
|
184
|
+
containerPatternTile,
|
|
185
|
+
containerPatternSvg,
|
|
186
|
+
} from "./container-pattern.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -305,6 +305,14 @@ export interface WidgetDatastoreTemplateTable {
|
|
|
305
305
|
* upgrade that declares it turns it on for the existing table.
|
|
306
306
|
*/
|
|
307
307
|
presence?: boolean;
|
|
308
|
+
/**
|
|
309
|
+
* When true, a row is deleted for good, with every row a CASCADE relation
|
|
310
|
+
* ties to it, once a signed-in member giving up their own grant leaves it
|
|
311
|
+
* with none and no table-wide grant lets anyone read it. A row that never had a grant and a
|
|
312
|
+
* revoke made in the Studio never delete. An upgrade that declares it turns
|
|
313
|
+
* it on for the existing table.
|
|
314
|
+
*/
|
|
315
|
+
deleteWhenLastMemberLeaves?: boolean;
|
|
308
316
|
/**
|
|
309
317
|
* REQ-TEMPLATES-ACL: optional table-level public grant. Omitted means
|
|
310
318
|
* the table starts purely per-record (record-level grants are the only
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.285.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -4147,7 +4147,7 @@ const MANIFEST_SCHEMA = {
|
|
|
4147
4147
|
type: "object",
|
|
4148
4148
|
required: false,
|
|
4149
4149
|
description:
|
|
4150
|
-
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
4150
|
+
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 32 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A table whose rows people join and leave through per-record grants (conversations) may declare `deleteWhenLastMemberLeaves: true`: when a signed-in member giving up their own grant leaves a row with no grant and no table-wide grant lets anyone read it, the row and every row a CASCADE relation ties to it (including rows deleted earlier) are deleted for good. A row that never had a grant (a seeded sample row) is never deleted this way, and a revoke made in the Studio never deletes. An upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
4151
4151
|
},
|
|
4152
4152
|
translations: {
|
|
4153
4153
|
type: "object",
|
|
@@ -6713,7 +6713,9 @@ const CONTRACT = deepFreeze({
|
|
|
6713
6713
|
// page at a time, with loadMore / hasMore / retry) and
|
|
6714
6714
|
// `pageSizeProperty`, exported as `PAGE_SIZE_PROPERTY`: the standard
|
|
6715
6715
|
// "Rows per page" property.
|
|
6716
|
-
|
|
6716
|
+
// 1.233.0: additive (sc-8115) — a `datastoreTemplate` table may declare
|
|
6717
|
+
// `deleteWhenLastMemberLeaves: true`.
|
|
6718
|
+
version: "1.233.0",
|
|
6717
6719
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6718
6720
|
hooks: HOOKS,
|
|
6719
6721
|
primitives: PRIMITIVES,
|