@wildwinter/app-shell 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/index.cjs +44 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens.css +105 -0
- package/dist/tokens.css.map +1 -0
- package/dist/tokens.d.cts +2 -0
- package/dist/tokens.d.ts +2 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ian Thomas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @wildwinter/app-shell
|
|
2
|
+
|
|
3
|
+
The shared shell for the editor family (Patterpad, Storylet Studio): the
|
|
4
|
+
app-agnostic pieces both apps would otherwise hand-mirror. Framework-neutral
|
|
5
|
+
(vanilla TS + DOM), no app dependencies.
|
|
6
|
+
|
|
7
|
+
**Two palettes, one grammar.** Each app supplies its own palette and fonts; this
|
|
8
|
+
package supplies the grammar and primitives beneath them.
|
|
9
|
+
|
|
10
|
+
## v0.1 (the thin slice)
|
|
11
|
+
|
|
12
|
+
- **`el`** - the tag-typed element factory. A superset of the two apps' prior
|
|
13
|
+
signatures, so neither rewrites call sites:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { el } from "@wildwinter/app-shell";
|
|
17
|
+
el("div", "card", "hello"); // string className + text child
|
|
18
|
+
el("button", { className: "b", onClick: fn }, child); // props bag + children
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **`colourIndex` / `colourFor` / `PALETTE` / `PALETTE_SIZE`** - the per-entity
|
|
22
|
+
colour hash. A stable name -> palette-slot index, storage-free. The hues live
|
|
23
|
+
in each theme as `--char-0 .. --char-11`.
|
|
24
|
+
|
|
25
|
+
- **`tokens.css`** - the design-token grammar (elevation, motion, radius,
|
|
26
|
+
scrollbar). Import once at the app root:
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
@import "@wildwinter/app-shell/tokens.css";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Roadmap
|
|
33
|
+
|
|
34
|
+
Later slices lift the rest of the common shell from Patterpad (canonical),
|
|
35
|
+
reconciling Storylet Studio's variants: the pane grid + toggles, the save/dirty
|
|
36
|
+
controller, the command palette, the anchored popover/panel + toast, and the
|
|
37
|
+
detached-window out-of-date / restart pattern. Storylet Studio migrates each
|
|
38
|
+
first (proving app-agnosticism); Patterpad follows.
|
|
39
|
+
|
|
40
|
+
## Release
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm run release -- minor # bumps, commits, pushes, tags, creates a GH Release
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Creating the Release triggers `publish.yml`, which publishes to npm. Requires an
|
|
47
|
+
`NPM_TOKEN` repo secret with publish rights to the `@wildwinter` scope.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/dom.ts
|
|
4
|
+
function el(tag, classNameOrProps, ...children) {
|
|
5
|
+
const node = document.createElement(tag);
|
|
6
|
+
const props = typeof classNameOrProps === "string" ? { className: classNameOrProps } : classNameOrProps ?? {};
|
|
7
|
+
if (props.className) node.className = props.className;
|
|
8
|
+
if (props.text !== void 0 && props.text !== null) node.textContent = props.text;
|
|
9
|
+
if (props.title !== void 0) node.title = props.title;
|
|
10
|
+
if (props.onClick) node.addEventListener("click", props.onClick);
|
|
11
|
+
for (const child of children) {
|
|
12
|
+
if (child === null || child === void 0) continue;
|
|
13
|
+
node.append(child);
|
|
14
|
+
}
|
|
15
|
+
return node;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/colour.ts
|
|
19
|
+
var PALETTE_SIZE = 12;
|
|
20
|
+
var PALETTE = Array.from({ length: PALETTE_SIZE }, (_, i) => `var(--char-${i})`);
|
|
21
|
+
function colourIndex(name) {
|
|
22
|
+
let h = 2166136261;
|
|
23
|
+
for (let i = 0; i < name.length; i++) {
|
|
24
|
+
h ^= name.charCodeAt(i);
|
|
25
|
+
h = Math.imul(h, 16777619);
|
|
26
|
+
}
|
|
27
|
+
h ^= h >>> 16;
|
|
28
|
+
h = Math.imul(h, 2246822507);
|
|
29
|
+
h ^= h >>> 13;
|
|
30
|
+
h = Math.imul(h, 3266489909);
|
|
31
|
+
h ^= h >>> 16;
|
|
32
|
+
return (h >>> 0) % PALETTE_SIZE;
|
|
33
|
+
}
|
|
34
|
+
function colourFor(name) {
|
|
35
|
+
return PALETTE[colourIndex(name)];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.PALETTE = PALETTE;
|
|
39
|
+
exports.PALETTE_SIZE = PALETTE_SIZE;
|
|
40
|
+
exports.colourFor = colourFor;
|
|
41
|
+
exports.colourIndex = colourIndex;
|
|
42
|
+
exports.el = el;
|
|
43
|
+
//# sourceMappingURL=index.cjs.map
|
|
44
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dom.ts","../src/colour.ts"],"names":[],"mappings":";;;AAqBO,SAAS,EAAA,CACd,GAAA,EACA,gBAAA,EAAA,GACG,QAAA,EACuB;AAC1B,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACvC,EAAA,MAAM,KAAA,GAAiB,OAAO,gBAAA,KAAqB,QAAA,GAC/C,EAAE,SAAA,EAAW,gBAAA,EAAiB,GAC9B,gBAAA,IAAoB,EAAC;AACzB,EAAA,IAAI,KAAA,CAAM,SAAA,EAAW,IAAA,CAAK,SAAA,GAAY,KAAA,CAAM,SAAA;AAC5C,EAAA,IAAI,KAAA,CAAM,SAAS,MAAA,IAAa,KAAA,CAAM,SAAS,IAAA,EAAM,IAAA,CAAK,cAAc,KAAA,CAAM,IAAA;AAC9E,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,QAAQ,KAAA,CAAM,KAAA;AAClD,EAAA,IAAI,MAAM,OAAA,EAAS,IAAA,CAAK,gBAAA,CAAiB,OAAA,EAAS,MAAM,OAAwB,CAAA;AAChF,EAAA,KAAA,MAAW,SAAS,QAAA,EAAU;AAC5B,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW;AAC3C,IAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,EACnB;AACA,EAAA,OAAO,IAAA;AACT;;;AC3BO,IAAM,YAAA,GAAe;AAErB,IAAM,OAAA,GAAU,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,YAAA,EAAa,EAAG,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,WAAA,EAAc,CAAC,CAAA,CAAA,CAAG;AAIjF,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,IAAI,CAAA,GAAI,UAAA;AACR,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AAAE,IAAA,CAAA,IAAK,IAAA,CAAK,WAAW,CAAC,CAAA;AAAG,IAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,QAAU,CAAA;AAAA,EAAG;AAC/F,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AAAI,EAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,UAAU,CAAA;AAAG,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AAAI,EAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,UAAU,CAAA;AAAG,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AACrG,EAAA,OAAA,CAAQ,MAAM,CAAA,IAAK,YAAA;AACrB;AAGO,SAAS,UAAU,IAAA,EAAsB;AAC9C,EAAA,OAAO,OAAA,CAAQ,WAAA,CAAY,IAAI,CAAC,CAAA;AAClC","file":"index.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// `el`: the tiny tag-typed element factory both apps build their imperative,\n// idempotent views with.\n//\n// A SUPERSET of the two apps' prior signatures, so neither rewrites call sites:\n// - Patterpad: el(tag, cls?, text?) (positional)\n// - Storylet Studio: el(tag, { className, text, ... }, ...kids) (options bag)\n// A string second argument is the className; an object is the props bag; string\n// children become text nodes. So `el(\"div\", \"c\", \"t\")` and\n// `el(\"div\", { className: \"c\" }, \"t\")` produce identical DOM.\n// ---------------------------------------------------------------------------\n\nexport type Child = Node | string | null | undefined;\n\nexport interface ElProps {\n className?: string;\n text?: string;\n title?: string;\n onClick?: (event: MouseEvent) => void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n classNameOrProps?: string | ElProps,\n ...children: Child[]\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n const props: ElProps = typeof classNameOrProps === \"string\"\n ? { className: classNameOrProps }\n : classNameOrProps ?? {};\n if (props.className) node.className = props.className;\n if (props.text !== undefined && props.text !== null) node.textContent = props.text;\n if (props.title !== undefined) node.title = props.title;\n if (props.onClick) node.addEventListener(\"click\", props.onClick as EventListener);\n for (const child of children) {\n if (child === null || child === undefined) continue;\n node.append(child);\n }\n return node;\n}\n","// ---------------------------------------------------------------------------\n// The per-entity colour model (design-language.md section 4): a stable hash of\n// a name selects an INDEX into the curated palette - repeatable, storage-free.\n// The hues live in the token layer as `--char-0 .. --char-(N-1)` per theme, so\n// a colour reference adapts to light/dark with zero JS. Each app defines the\n// `--char-*` values in its own palette; this module only picks the slot.\n//\n// Storylet Studio uses it for dimension-value / zone chips; Patterpad for\n// character cues.\n// ---------------------------------------------------------------------------\n\n/** Curated colour slots; must match the `--char-*` set defined per theme. */\nexport const PALETTE_SIZE = 12;\n\nexport const PALETTE = Array.from({ length: PALETTE_SIZE }, (_, i) => `var(--char-${i})`) as readonly string[];\n\n/** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well\n * distributed even for short, similar names. */\nexport function colourIndex(name: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < name.length; i++) { h ^= name.charCodeAt(i); h = Math.imul(h, 0x01000193); }\n h ^= h >>> 16; h = Math.imul(h, 0x85ebca6b); h ^= h >>> 13; h = Math.imul(h, 0xc2b2ae35); h ^= h >>> 16;\n return (h >>> 0) % PALETTE_SIZE;\n}\n\n/** The theme-aware CSS variable reference for a name's slot. */\nexport function colourFor(name: string): string {\n return PALETTE[colourIndex(name)]!;\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type Child = Node | string | null | undefined;
|
|
2
|
+
interface ElProps {
|
|
3
|
+
className?: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
onClick?: (event: MouseEvent) => void;
|
|
7
|
+
}
|
|
8
|
+
declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, classNameOrProps?: string | ElProps, ...children: Child[]): HTMLElementTagNameMap[K];
|
|
9
|
+
|
|
10
|
+
/** Curated colour slots; must match the `--char-*` set defined per theme. */
|
|
11
|
+
declare const PALETTE_SIZE = 12;
|
|
12
|
+
declare const PALETTE: readonly string[];
|
|
13
|
+
/** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well
|
|
14
|
+
* distributed even for short, similar names. */
|
|
15
|
+
declare function colourIndex(name: string): number;
|
|
16
|
+
/** The theme-aware CSS variable reference for a name's slot. */
|
|
17
|
+
declare function colourFor(name: string): string;
|
|
18
|
+
|
|
19
|
+
export { type Child, type ElProps, PALETTE, PALETTE_SIZE, colourFor, colourIndex, el };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type Child = Node | string | null | undefined;
|
|
2
|
+
interface ElProps {
|
|
3
|
+
className?: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
onClick?: (event: MouseEvent) => void;
|
|
7
|
+
}
|
|
8
|
+
declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, classNameOrProps?: string | ElProps, ...children: Child[]): HTMLElementTagNameMap[K];
|
|
9
|
+
|
|
10
|
+
/** Curated colour slots; must match the `--char-*` set defined per theme. */
|
|
11
|
+
declare const PALETTE_SIZE = 12;
|
|
12
|
+
declare const PALETTE: readonly string[];
|
|
13
|
+
/** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well
|
|
14
|
+
* distributed even for short, similar names. */
|
|
15
|
+
declare function colourIndex(name: string): number;
|
|
16
|
+
/** The theme-aware CSS variable reference for a name's slot. */
|
|
17
|
+
declare function colourFor(name: string): string;
|
|
18
|
+
|
|
19
|
+
export { type Child, type ElProps, PALETTE, PALETTE_SIZE, colourFor, colourIndex, el };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/dom.ts
|
|
2
|
+
function el(tag, classNameOrProps, ...children) {
|
|
3
|
+
const node = document.createElement(tag);
|
|
4
|
+
const props = typeof classNameOrProps === "string" ? { className: classNameOrProps } : classNameOrProps ?? {};
|
|
5
|
+
if (props.className) node.className = props.className;
|
|
6
|
+
if (props.text !== void 0 && props.text !== null) node.textContent = props.text;
|
|
7
|
+
if (props.title !== void 0) node.title = props.title;
|
|
8
|
+
if (props.onClick) node.addEventListener("click", props.onClick);
|
|
9
|
+
for (const child of children) {
|
|
10
|
+
if (child === null || child === void 0) continue;
|
|
11
|
+
node.append(child);
|
|
12
|
+
}
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/colour.ts
|
|
17
|
+
var PALETTE_SIZE = 12;
|
|
18
|
+
var PALETTE = Array.from({ length: PALETTE_SIZE }, (_, i) => `var(--char-${i})`);
|
|
19
|
+
function colourIndex(name) {
|
|
20
|
+
let h = 2166136261;
|
|
21
|
+
for (let i = 0; i < name.length; i++) {
|
|
22
|
+
h ^= name.charCodeAt(i);
|
|
23
|
+
h = Math.imul(h, 16777619);
|
|
24
|
+
}
|
|
25
|
+
h ^= h >>> 16;
|
|
26
|
+
h = Math.imul(h, 2246822507);
|
|
27
|
+
h ^= h >>> 13;
|
|
28
|
+
h = Math.imul(h, 3266489909);
|
|
29
|
+
h ^= h >>> 16;
|
|
30
|
+
return (h >>> 0) % PALETTE_SIZE;
|
|
31
|
+
}
|
|
32
|
+
function colourFor(name) {
|
|
33
|
+
return PALETTE[colourIndex(name)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { PALETTE, PALETTE_SIZE, colourFor, colourIndex, el };
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dom.ts","../src/colour.ts"],"names":[],"mappings":";AAqBO,SAAS,EAAA,CACd,GAAA,EACA,gBAAA,EAAA,GACG,QAAA,EACuB;AAC1B,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACvC,EAAA,MAAM,KAAA,GAAiB,OAAO,gBAAA,KAAqB,QAAA,GAC/C,EAAE,SAAA,EAAW,gBAAA,EAAiB,GAC9B,gBAAA,IAAoB,EAAC;AACzB,EAAA,IAAI,KAAA,CAAM,SAAA,EAAW,IAAA,CAAK,SAAA,GAAY,KAAA,CAAM,SAAA;AAC5C,EAAA,IAAI,KAAA,CAAM,SAAS,MAAA,IAAa,KAAA,CAAM,SAAS,IAAA,EAAM,IAAA,CAAK,cAAc,KAAA,CAAM,IAAA;AAC9E,EAAA,IAAI,KAAA,CAAM,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,QAAQ,KAAA,CAAM,KAAA;AAClD,EAAA,IAAI,MAAM,OAAA,EAAS,IAAA,CAAK,gBAAA,CAAiB,OAAA,EAAS,MAAM,OAAwB,CAAA;AAChF,EAAA,KAAA,MAAW,SAAS,QAAA,EAAU;AAC5B,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW;AAC3C,IAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,EACnB;AACA,EAAA,OAAO,IAAA;AACT;;;AC3BO,IAAM,YAAA,GAAe;AAErB,IAAM,OAAA,GAAU,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,YAAA,EAAa,EAAG,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,WAAA,EAAc,CAAC,CAAA,CAAA,CAAG;AAIjF,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,IAAI,CAAA,GAAI,UAAA;AACR,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AAAE,IAAA,CAAA,IAAK,IAAA,CAAK,WAAW,CAAC,CAAA;AAAG,IAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,QAAU,CAAA;AAAA,EAAG;AAC/F,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AAAI,EAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,UAAU,CAAA;AAAG,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AAAI,EAAA,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,UAAU,CAAA;AAAG,EAAA,CAAA,IAAK,CAAA,KAAM,EAAA;AACrG,EAAA,OAAA,CAAQ,MAAM,CAAA,IAAK,YAAA;AACrB;AAGO,SAAS,UAAU,IAAA,EAAsB;AAC9C,EAAA,OAAO,OAAA,CAAQ,WAAA,CAAY,IAAI,CAAC,CAAA;AAClC","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `el`: the tiny tag-typed element factory both apps build their imperative,\n// idempotent views with.\n//\n// A SUPERSET of the two apps' prior signatures, so neither rewrites call sites:\n// - Patterpad: el(tag, cls?, text?) (positional)\n// - Storylet Studio: el(tag, { className, text, ... }, ...kids) (options bag)\n// A string second argument is the className; an object is the props bag; string\n// children become text nodes. So `el(\"div\", \"c\", \"t\")` and\n// `el(\"div\", { className: \"c\" }, \"t\")` produce identical DOM.\n// ---------------------------------------------------------------------------\n\nexport type Child = Node | string | null | undefined;\n\nexport interface ElProps {\n className?: string;\n text?: string;\n title?: string;\n onClick?: (event: MouseEvent) => void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n classNameOrProps?: string | ElProps,\n ...children: Child[]\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n const props: ElProps = typeof classNameOrProps === \"string\"\n ? { className: classNameOrProps }\n : classNameOrProps ?? {};\n if (props.className) node.className = props.className;\n if (props.text !== undefined && props.text !== null) node.textContent = props.text;\n if (props.title !== undefined) node.title = props.title;\n if (props.onClick) node.addEventListener(\"click\", props.onClick as EventListener);\n for (const child of children) {\n if (child === null || child === undefined) continue;\n node.append(child);\n }\n return node;\n}\n","// ---------------------------------------------------------------------------\n// The per-entity colour model (design-language.md section 4): a stable hash of\n// a name selects an INDEX into the curated palette - repeatable, storage-free.\n// The hues live in the token layer as `--char-0 .. --char-(N-1)` per theme, so\n// a colour reference adapts to light/dark with zero JS. Each app defines the\n// `--char-*` values in its own palette; this module only picks the slot.\n//\n// Storylet Studio uses it for dimension-value / zone chips; Patterpad for\n// character cues.\n// ---------------------------------------------------------------------------\n\n/** Curated colour slots; must match the `--char-*` set defined per theme. */\nexport const PALETTE_SIZE = 12;\n\nexport const PALETTE = Array.from({ length: PALETTE_SIZE }, (_, i) => `var(--char-${i})`) as readonly string[];\n\n/** Hash a name to a palette slot: FNV-1a + an fmix32 avalanche, well\n * distributed even for short, similar names. */\nexport function colourIndex(name: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < name.length; i++) { h ^= name.charCodeAt(i); h = Math.imul(h, 0x01000193); }\n h ^= h >>> 16; h = Math.imul(h, 0x85ebca6b); h ^= h >>> 13; h = Math.imul(h, 0xc2b2ae35); h ^= h >>> 16;\n return (h >>> 0) % PALETTE_SIZE;\n}\n\n/** The theme-aware CSS variable reference for a name's slot. */\nexport function colourFor(name: string): string {\n return PALETTE[colourIndex(name)]!;\n}\n"]}
|
package/dist/tokens.css
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* src/tokens.css */
|
|
2
|
+
:root {
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
--shadow-1: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
5
|
+
--shadow-2: 0 6px 20px rgba(0, 0, 0, 0.14);
|
|
6
|
+
--shadow-3: 0 16px 48px rgba(0, 0, 0, 0.28);
|
|
7
|
+
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
8
|
+
--dur-quick: 120ms;
|
|
9
|
+
--dur-base: 150ms;
|
|
10
|
+
--dur-settle: 200ms;
|
|
11
|
+
--dur-slow: 1s;
|
|
12
|
+
--dur-panel: 260ms;
|
|
13
|
+
--dur-exit: 170ms;
|
|
14
|
+
--anim-panel-in: panel-in var(--dur-panel) var(--ease-standard);
|
|
15
|
+
--anim-backdrop-in: backdrop-in var(--dur-settle) var(--ease-standard);
|
|
16
|
+
--anim-screen-in: screen-in var(--dur-panel) var(--ease-standard);
|
|
17
|
+
--anim-panel-out: panel-out var(--dur-exit) var(--ease-standard) forwards;
|
|
18
|
+
--anim-backdrop-out: backdrop-out var(--dur-exit) var(--ease-standard) forwards;
|
|
19
|
+
--radius-sm: 6px;
|
|
20
|
+
--radius-md: 10px;
|
|
21
|
+
--radius-lg: 14px;
|
|
22
|
+
--radius-pill: 999px;
|
|
23
|
+
--scroll-thumb: color-mix(in oklab, var(--ink) 20%, transparent);
|
|
24
|
+
--scroll-thumb-hover: color-mix(in oklab, var(--ink) 40%, transparent);
|
|
25
|
+
}
|
|
26
|
+
* {
|
|
27
|
+
scrollbar-width: thin;
|
|
28
|
+
scrollbar-color: var(--scroll-thumb) transparent;
|
|
29
|
+
}
|
|
30
|
+
::-webkit-scrollbar {
|
|
31
|
+
width: 11px;
|
|
32
|
+
height: 11px;
|
|
33
|
+
}
|
|
34
|
+
::-webkit-scrollbar-track {
|
|
35
|
+
background: transparent;
|
|
36
|
+
}
|
|
37
|
+
::-webkit-scrollbar-corner {
|
|
38
|
+
background: transparent;
|
|
39
|
+
}
|
|
40
|
+
::-webkit-scrollbar-thumb {
|
|
41
|
+
background: var(--scroll-thumb);
|
|
42
|
+
border-radius: 8px;
|
|
43
|
+
border: 3px solid transparent;
|
|
44
|
+
background-clip: padding-box;
|
|
45
|
+
}
|
|
46
|
+
::-webkit-scrollbar-thumb:hover {
|
|
47
|
+
background: var(--scroll-thumb-hover);
|
|
48
|
+
background-clip: padding-box;
|
|
49
|
+
}
|
|
50
|
+
@keyframes panel-in {
|
|
51
|
+
from {
|
|
52
|
+
opacity: 0;
|
|
53
|
+
transform: translateY(-9px) scale(0.965);
|
|
54
|
+
}
|
|
55
|
+
to {
|
|
56
|
+
opacity: 1;
|
|
57
|
+
transform: none;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
@keyframes backdrop-in {
|
|
61
|
+
from {
|
|
62
|
+
opacity: 0;
|
|
63
|
+
}
|
|
64
|
+
to {
|
|
65
|
+
opacity: 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
@keyframes screen-in {
|
|
69
|
+
from {
|
|
70
|
+
opacity: 0;
|
|
71
|
+
transform: translateY(6px);
|
|
72
|
+
}
|
|
73
|
+
to {
|
|
74
|
+
opacity: 1;
|
|
75
|
+
transform: none;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
@keyframes panel-out {
|
|
79
|
+
from {
|
|
80
|
+
opacity: 1;
|
|
81
|
+
transform: none;
|
|
82
|
+
}
|
|
83
|
+
to {
|
|
84
|
+
opacity: 0;
|
|
85
|
+
transform: translateY(-6px) scale(0.97);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
@keyframes backdrop-out {
|
|
89
|
+
from {
|
|
90
|
+
opacity: 1;
|
|
91
|
+
}
|
|
92
|
+
to {
|
|
93
|
+
opacity: 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
@media (prefers-reduced-motion: reduce) {
|
|
97
|
+
:root {
|
|
98
|
+
--anim-panel-in: none;
|
|
99
|
+
--anim-backdrop-in: none;
|
|
100
|
+
--anim-screen-in: none;
|
|
101
|
+
--anim-panel-out: none;
|
|
102
|
+
--anim-backdrop-out: none;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/*# sourceMappingURL=tokens.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tokens.css"],"sourcesContent":["/* ============================================================================\n Design-token GRAMMAR - the app-agnostic layer (design-language.md section 4).\n\n The shared half of \"two palettes, one grammar\": the scales that are IDENTICAL\n across the editor family - elevation, motion, radius, scrollbar chrome. Each\n app supplies its own colour + font VALUES (its palette) over this grammar in\n its own theme.css. Do not add app-specific colours here.\n ============================================================================ */\n\n:root {\n color-scheme: light dark;\n\n /* Elevation: three tiers by how far a surface floats (resting cards,\n floating menus/popovers, full overlays). Theme-agnostic black so it\n deepens naturally on the dark palettes. */\n --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.05);\n --shadow-2: 0 6px 20px rgba(0, 0, 0, 0.14);\n --shadow-3: 0 16px 48px rgba(0, 0, 0, 0.28);\n\n /* Motion: one easing curve + a small duration scale, plus the shared\n panel/backdrop/screen enter-exit vocabulary. Nothing pops. */\n --ease-standard: cubic-bezier(0.2, 0, 0, 1);\n --dur-quick: 120ms;\n --dur-base: 150ms;\n --dur-settle: 200ms;\n --dur-slow: 1s;\n --dur-panel: 260ms;\n --dur-exit: 170ms;\n --anim-panel-in: panel-in var(--dur-panel) var(--ease-standard);\n --anim-backdrop-in: backdrop-in var(--dur-settle) var(--ease-standard);\n --anim-screen-in: screen-in var(--dur-panel) var(--ease-standard);\n --anim-panel-out: panel-out var(--dur-exit) var(--ease-standard) forwards;\n --anim-backdrop-out: backdrop-out var(--dur-exit) var(--ease-standard) forwards;\n\n /* Corner radius: four steps cover every surface. */\n --radius-sm: 6px;\n --radius-md: 10px;\n --radius-lg: 14px;\n --radius-pill: 999px;\n\n /* Scrollbar thumb: a translucent ink, so it adapts per palette. */\n --scroll-thumb: color-mix(in oklab, var(--ink) 20%, transparent);\n --scroll-thumb-hover: color-mix(in oklab, var(--ink) 40%, transparent);\n}\n\n* { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }\n::-webkit-scrollbar { width: 11px; height: 11px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-corner { background: transparent; }\n::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 8px; border: 3px solid transparent; background-clip: padding-box; }\n::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }\n\n@keyframes panel-in { from { opacity: 0; transform: translateY(-9px) scale(0.965); } to { opacity: 1; transform: none; } }\n@keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes screen-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }\n@keyframes panel-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-6px) scale(0.97); } }\n@keyframes backdrop-out { from { opacity: 1; } to { opacity: 0; } }\n\n@media (prefers-reduced-motion: reduce) {\n :root {\n --anim-panel-in: none; --anim-backdrop-in: none; --anim-screen-in: none;\n --anim-panel-out: none; --anim-backdrop-out: none;\n }\n}\n"],"mappings":";AASA;AACE,gBAAc,MAAM;AAKpB,cAAY,EAAE,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpC,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrC,cAAY,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAItC,mBAAiB,aAAa,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE;AACzC,eAAa;AACb,cAAY;AACZ,gBAAc;AACd,cAAY;AACZ,eAAa;AACb,cAAY;AACZ,mBAAiB,SAAS,IAAI,aAAa,IAAI;AAC/C,sBAAoB,YAAY,IAAI,cAAc,IAAI;AACtD,oBAAkB,UAAU,IAAI,aAAa,IAAI;AACjD,oBAAkB,UAAU,IAAI,YAAY,IAAI,iBAAiB;AACjE,uBAAqB,aAAa,IAAI,YAAY,IAAI,iBAAiB;AAGvE,eAAa;AACb,eAAa;AACb,eAAa;AACb,iBAAe;AAGf,kBAAgB,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AACpD,wBAAsB,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AAC5D;AAEA;AAAI,mBAAiB;AAAM,mBAAiB,IAAI,gBAAgB;AAAa;AAC7E;AAAsB,SAAO;AAAM,UAAQ;AAAM;AACjD;AAA4B,cAAY;AAAa;AACrD;AAA6B,cAAY;AAAa;AACtD;AAA4B,cAAY,IAAI;AAAiB,iBAAe;AAAK,UAAQ,IAAI,MAAM;AAAa,mBAAiB;AAAa;AAC9I,yBAAyB;AAAS,cAAY,IAAI;AAAuB,mBAAiB;AAAa;AAEvG,WAAW;AAAW;AAAO,aAAS;AAAG,eAAW,WAAW,MAAM,MAAM;AAAQ;AAAE;AAAK,aAAS;AAAG,eAAW;AAAM;AAAE;AACzH,WAAW;AAAc;AAAO,aAAS;AAAG;AAAE;AAAK,aAAS;AAAG;AAAE;AACjE,WAAW;AAAY;AAAO,aAAS;AAAG,eAAW,WAAW;AAAM;AAAE;AAAK,aAAS;AAAG,eAAW;AAAM;AAAE;AAC5G,WAAW;AAAY;AAAO,aAAS;AAAG,eAAW;AAAM;AAAE;AAAK,aAAS;AAAG,eAAW,WAAW,MAAM,MAAM;AAAO;AAAE;AACzH,WAAW;AAAe;AAAO,aAAS;AAAG;AAAE;AAAK,aAAS;AAAG;AAAE;AAElE,QAAO,wBAAyB;AAC9B;AACE,qBAAiB;AAAM,wBAAoB;AAAM,sBAAkB;AACnE,sBAAkB;AAAM,yBAAqB;AAC/C;AACF;","names":[]}
|
package/dist/tokens.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wildwinter/app-shell",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The shared shell for the editor family (Patterpad, Storylet Studio): the app-agnostic design-token grammar, the per-entity colour hash, and the vanilla-TS DOM primitive. Two palettes, one grammar - each app supplies its own palette/fonts over these tokens.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Ian Thomas",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/wildwinter/app-shell.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"shell",
|
|
14
|
+
"design-tokens",
|
|
15
|
+
"dom",
|
|
16
|
+
"electron",
|
|
17
|
+
"wildwinter"
|
|
18
|
+
],
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./tokens.css": "./dist/tokens.css"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"sideEffects": [
|
|
36
|
+
"*.css"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"registry": "https://registry.npmjs.org",
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"release": "node scripts/release.mjs",
|
|
47
|
+
"prepublishOnly": "npm run build && npm test"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"jsdom": "^25.0.0",
|
|
51
|
+
"tsup": "^8.3.5",
|
|
52
|
+
"typescript": "^5.6.3",
|
|
53
|
+
"vitest": "^3.2.7"
|
|
54
|
+
}
|
|
55
|
+
}
|