@kumikijs/runtime 0.3.0 → 0.4.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/dist/index.d.ts +63 -5
- package/dist/index.js +148 -34
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
//#region src/element.d.ts
|
|
2
|
+
/** Maps one observed attribute to a slot, with an optional parser (default: raw string). */
|
|
3
|
+
type AttributeSlotBinding = {
|
|
4
|
+
slot: string;
|
|
5
|
+
parse?: (raw: string | null) => unknown;
|
|
6
|
+
};
|
|
7
|
+
type KumikiElementOptions = {
|
|
8
|
+
/** Host implementations for custom capabilities (the inbound seam), forwarded to mount. */providers?: Record<string, CapabilityProvider>;
|
|
9
|
+
/**
|
|
10
|
+
* Custom-capability names to surface as DOM CustomEvents on the element. For
|
|
11
|
+
* each, emitting the effect dispatches `CustomEvent(cap, { detail: input })`
|
|
12
|
+
* (bubbling, composed) and resolves ok — so a host can bind via
|
|
13
|
+
* `addEventListener(cap, …)` / framework `@cap`. A `providers[cap]` entry
|
|
14
|
+
* takes precedence over the passthrough for the same capability.
|
|
15
|
+
*/
|
|
16
|
+
events?: string[]; /** Observed attributes mapped to slots; updates flow in on connect and on change. */
|
|
17
|
+
attributeSlots?: Record<string, AttributeSlotBinding>;
|
|
18
|
+
/**
|
|
19
|
+
* Render into an open shadow root for full style isolation: the app's
|
|
20
|
+
* theme/motion/state `<style>` nodes are injected into the shadow root (not the
|
|
21
|
+
* document head), so host-page CSS does not bleed in and Kumiki's CSS does not
|
|
22
|
+
* leak out. Default: false (light DOM — the runtime's document-level styles
|
|
23
|
+
* apply, matching a standalone Kumiki page).
|
|
24
|
+
*/
|
|
25
|
+
shadow?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Register `app` as the custom element `tagName`. Idempotent: if the tag is
|
|
29
|
+
* already defined this is a no-op (so re-imports / HMR don't throw). Requires a
|
|
30
|
+
* DOM environment.
|
|
31
|
+
*/
|
|
32
|
+
declare function defineKumikiElement(tagName: string, app: AppShape | (() => AppShape), options?: KumikiElementOptions): void;
|
|
33
|
+
//#endregion
|
|
1
34
|
//#region src/scenario.d.ts
|
|
2
35
|
/** One thing to do to the app. Exactly one field should be set. */
|
|
3
36
|
type Action = {
|
|
@@ -87,7 +120,7 @@ declare function smoke(app: AppShape, root: HTMLElement, opts?: SmokeOptions): P
|
|
|
87
120
|
type RefinementCheck = (v: unknown) => boolean;
|
|
88
121
|
type EventHandler = (el: Record<string, unknown>) => void;
|
|
89
122
|
/**
|
|
90
|
-
* A controlled panic — Kumiki's "stop the program" signal (spec/stdlib.md §2.2:
|
|
123
|
+
* A controlled panic — Kumiki's "stop the program" signal (docs/spec/stdlib.md §2.2:
|
|
91
124
|
* `panic(message)`; `Option/Result.get` on the empty case; `Result.get-err` on
|
|
92
125
|
* `Ok`). On the live path a panic is caught — the dispatch episode is rolled
|
|
93
126
|
* back (no partial slot writes) and an error boundary / top-level fallback is
|
|
@@ -264,8 +297,33 @@ type EffectResult = {
|
|
|
264
297
|
kind: "err";
|
|
265
298
|
value: unknown;
|
|
266
299
|
};
|
|
300
|
+
/**
|
|
301
|
+
* A host-supplied implementation for a custom capability (one registered via
|
|
302
|
+
* `kumiki.caps.json`). This is Kumiki's inbound ecosystem seam: arbitrary JS /
|
|
303
|
+
* npm libraries live here, behind a typed, mockable capability boundary, so the
|
|
304
|
+
* Kumiki core stays pure (no language-level FFI). `input` is the effect's
|
|
305
|
+
* (already `map-request`-mapped) request; the return may be sync or async.
|
|
306
|
+
*/
|
|
307
|
+
type CapabilityProvider = (input: unknown, caps: CapabilityRegistry) => Promise<EffectResult> | EffectResult;
|
|
267
308
|
type CapabilityRegistry = {
|
|
268
|
-
has(cap: string): boolean;
|
|
309
|
+
has(cap: string): boolean; /** The host provider registered for `cap` at mount, or undefined. */
|
|
310
|
+
provider(cap: string): CapabilityProvider | undefined;
|
|
311
|
+
};
|
|
312
|
+
/** Options accepted by `mount`. */
|
|
313
|
+
type MountOptions = {
|
|
314
|
+
/** Host implementations for custom capabilities, keyed by capability name. */providers?: Record<string, CapabilityProvider>;
|
|
315
|
+
/**
|
|
316
|
+
* Where Kumiki injects its `<style>` nodes (motion / theme / state styles).
|
|
317
|
+
* Defaults to `document` (styles go in `<head>`). Pass a `ShadowRoot` to keep
|
|
318
|
+
* them encapsulated — used by `defineKumikiElement({ shadow: true })`.
|
|
319
|
+
*/
|
|
320
|
+
styleRoot?: Document | ShadowRoot;
|
|
321
|
+
/**
|
|
322
|
+
* The element whose inline style carries theme background/foreground/font
|
|
323
|
+
* (the `<body>` equivalent). Defaults to `document.body`; the shadow element
|
|
324
|
+
* passes its in-shadow container so theming stays encapsulated.
|
|
325
|
+
*/
|
|
326
|
+
styleHost?: HTMLElement;
|
|
269
327
|
};
|
|
270
328
|
type RouteEntry = {
|
|
271
329
|
pattern: string; /** Returns the TileNode for this route given the current state. */
|
|
@@ -301,7 +359,7 @@ type AppShape = {
|
|
|
301
359
|
live?: Record<string, unknown>;
|
|
302
360
|
_rerender?: () => void;
|
|
303
361
|
};
|
|
304
|
-
declare function mount(app: AppShape, target: HTMLElement): {
|
|
362
|
+
declare function mount(app: AppShape, target: HTMLElement, options?: MountOptions): {
|
|
305
363
|
dispose: () => void;
|
|
306
364
|
};
|
|
307
365
|
type TestResult = {
|
|
@@ -386,7 +444,7 @@ declare const _stdlib: {
|
|
|
386
444
|
now(): number;
|
|
387
445
|
recordCopy(rec: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
|
|
388
446
|
/**
|
|
389
|
-
* `.get` — the polymorphic unwrap for Option AND Result. Per spec/stdlib.md
|
|
447
|
+
* `.get` — the polymorphic unwrap for Option AND Result. Per docs/spec/stdlib.md
|
|
390
448
|
* §2.2 it PANICS on the empty case (`None` / `Err`); `Some(v)` / `Ok(v)`
|
|
391
449
|
* unwrap to `v`. A plain (non-variant) value passes through unchanged. (Before
|
|
392
450
|
* v0.3 this returned the value unchanged on the empty case, so `.get` and
|
|
@@ -440,4 +498,4 @@ declare const builtinEffects: {
|
|
|
440
498
|
httpFetch(method: string, input: unknown, baseUrl: string): Promise<EffectResult>;
|
|
441
499
|
};
|
|
442
500
|
//#endregion
|
|
443
|
-
export { type Action, AppShape, CapabilityRegistry, EffectResult, type EffectScript, EffectSpec, EmitSpec, EventHandler, type Expect, KumikiPanic, RedirectEntry, ReducerSpec, RefinementCheck, RouteEntry, type Scenario, type ScenarioReport, type ScenarioStep, SlotMeta, type SmokeIssue, type SmokeOptions, type SmokePhase, type SmokeReport, type StepResult, TestResult, Theme, ThemeValue, TileNode, TileProps, _stdlib, builtinEffects, mount, runScenario, smoke };
|
|
501
|
+
export { type Action, AppShape, type AttributeSlotBinding, CapabilityProvider, CapabilityRegistry, EffectResult, type EffectScript, EffectSpec, EmitSpec, EventHandler, type Expect, type KumikiElementOptions, KumikiPanic, MountOptions, RedirectEntry, ReducerSpec, RefinementCheck, RouteEntry, type Scenario, type ScenarioReport, type ScenarioStep, SlotMeta, type SmokeIssue, type SmokeOptions, type SmokePhase, type SmokeReport, type StepResult, TestResult, Theme, ThemeValue, TileNode, TileProps, _stdlib, builtinEffects, defineKumikiElement, mount, runScenario, smoke };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,85 @@
|
|
|
1
|
+
//#region src/element.ts
|
|
2
|
+
/**
|
|
3
|
+
* Register `app` as the custom element `tagName`. Idempotent: if the tag is
|
|
4
|
+
* already defined this is a no-op (so re-imports / HMR don't throw). Requires a
|
|
5
|
+
* DOM environment.
|
|
6
|
+
*/
|
|
7
|
+
function defineKumikiElement(tagName, app, options = {}) {
|
|
8
|
+
if (typeof customElements === "undefined") throw new Error("defineKumikiElement requires a DOM environment (customElements is undefined).");
|
|
9
|
+
if (customElements.get(tagName)) return;
|
|
10
|
+
const makeApp = typeof app === "function" ? app : () => app;
|
|
11
|
+
const attributeSlots = options.attributeSlots ?? {};
|
|
12
|
+
const observed = Object.keys(attributeSlots);
|
|
13
|
+
const buildProviders = (el) => {
|
|
14
|
+
const merged = {};
|
|
15
|
+
for (const cap of options.events ?? []) merged[cap] = (input) => {
|
|
16
|
+
el.dispatchEvent(new CustomEvent(cap, {
|
|
17
|
+
detail: input,
|
|
18
|
+
bubbles: true,
|
|
19
|
+
composed: true
|
|
20
|
+
}));
|
|
21
|
+
return {
|
|
22
|
+
kind: "ok",
|
|
23
|
+
value: null
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
return Object.assign(merged, options.providers ?? {});
|
|
27
|
+
};
|
|
28
|
+
class KumikiAppElement extends HTMLElement {
|
|
29
|
+
static get observedAttributes() {
|
|
30
|
+
return observed;
|
|
31
|
+
}
|
|
32
|
+
handle = null;
|
|
33
|
+
app = null;
|
|
34
|
+
connectedCallback() {
|
|
35
|
+
if (this.handle) return;
|
|
36
|
+
this.app = makeApp();
|
|
37
|
+
let target = this;
|
|
38
|
+
const mountOpts = { providers: buildProviders(this) };
|
|
39
|
+
if (options.shadow) {
|
|
40
|
+
const root = this.shadowRoot ?? this.attachShadow({ mode: "open" });
|
|
41
|
+
root.replaceChildren();
|
|
42
|
+
const container = document.createElement("div");
|
|
43
|
+
root.appendChild(container);
|
|
44
|
+
target = container;
|
|
45
|
+
mountOpts.styleRoot = root;
|
|
46
|
+
mountOpts.styleHost = container;
|
|
47
|
+
}
|
|
48
|
+
this.handle = mount(this.app, target, mountOpts);
|
|
49
|
+
for (const attr of observed) if (this.hasAttribute(attr)) this.applyAttr(attr, this.getAttribute(attr));
|
|
50
|
+
}
|
|
51
|
+
disconnectedCallback() {
|
|
52
|
+
this.handle?.dispose();
|
|
53
|
+
this.handle = null;
|
|
54
|
+
}
|
|
55
|
+
attributeChangedCallback(name, _old, value) {
|
|
56
|
+
if (this.handle) this.applyAttr(name, value);
|
|
57
|
+
}
|
|
58
|
+
applyAttr(name, raw) {
|
|
59
|
+
const binding = attributeSlots[name];
|
|
60
|
+
if (!binding) return;
|
|
61
|
+
this.setSlot(binding.slot, binding.parse ? binding.parse(raw) : raw);
|
|
62
|
+
}
|
|
63
|
+
/** Write a live slot (respects its refinement) and re-render. */
|
|
64
|
+
setSlot(name, value) {
|
|
65
|
+
this.app?._setSlot?.(name, value);
|
|
66
|
+
}
|
|
67
|
+
/** Write several live slots at once. */
|
|
68
|
+
setSlots(values) {
|
|
69
|
+
for (const [name, value] of Object.entries(values)) this.setSlot(name, value);
|
|
70
|
+
}
|
|
71
|
+
/** Read a single live slot value. */
|
|
72
|
+
getSlot(name) {
|
|
73
|
+
return this.app?.live?.[name];
|
|
74
|
+
}
|
|
75
|
+
/** A snapshot of the current live slot values. */
|
|
76
|
+
get slots() {
|
|
77
|
+
return { ...this.app?.live ?? {} };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
customElements.define(tagName, KumikiAppElement);
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
1
83
|
//#region src/scenario.ts
|
|
2
84
|
const settle$1 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
3
85
|
async function runScenario(app, root, scenario, opts = {}) {
|
|
@@ -362,7 +444,7 @@ function errStr(e) {
|
|
|
362
444
|
//#endregion
|
|
363
445
|
//#region src/index.ts
|
|
364
446
|
/**
|
|
365
|
-
* A controlled panic — Kumiki's "stop the program" signal (spec/stdlib.md §2.2:
|
|
447
|
+
* A controlled panic — Kumiki's "stop the program" signal (docs/spec/stdlib.md §2.2:
|
|
366
448
|
* `panic(message)`; `Option/Result.get` on the empty case; `Result.get-err` on
|
|
367
449
|
* `Ok`). On the live path a panic is caught — the dispatch episode is rolled
|
|
368
450
|
* back (no partial slot writes) and an error boundary / top-level fallback is
|
|
@@ -439,15 +521,18 @@ function emptyRoute() {
|
|
|
439
521
|
hash: null
|
|
440
522
|
};
|
|
441
523
|
}
|
|
442
|
-
function mount(app, target) {
|
|
524
|
+
function mount(app, target, options = {}) {
|
|
443
525
|
if (!app.live) {
|
|
444
526
|
app.live = {};
|
|
445
527
|
for (const [k, v] of Object.entries(app.slots)) app.live[k] = v.value;
|
|
446
528
|
}
|
|
447
529
|
if (!("route" in app.live)) app.live.route = emptyRoute();
|
|
530
|
+
currentStyleRoot = options.styleRoot ?? document;
|
|
531
|
+
currentStyleHost = options.styleHost ?? null;
|
|
532
|
+
stateStylesEl = null;
|
|
448
533
|
ensureMotionStyles(app);
|
|
449
534
|
const slotValues = app.live;
|
|
450
|
-
const dispatcher = makeEffectDispatcher(app, makeCapabilityRegistry(app.caps), (effect, outcome, value, key) => {
|
|
535
|
+
const dispatcher = makeEffectDispatcher(app, makeCapabilityRegistry(app.caps, options.providers), (effect, outcome, value, key) => {
|
|
451
536
|
handleEffectResult(effect, outcome, value, key);
|
|
452
537
|
});
|
|
453
538
|
let currentRoot = null;
|
|
@@ -504,7 +589,7 @@ function mount(app, target) {
|
|
|
504
589
|
}
|
|
505
590
|
let inPanicHandler = false;
|
|
506
591
|
/**
|
|
507
|
-
* Handle a caught live panic per spec/lifecycle.md §7.2: the dispatch episode
|
|
592
|
+
* Handle a caught live panic per docs/spec/lifecycle.md §7.2: the dispatch episode
|
|
508
593
|
* is already rolled back (the caller never applied the failed result), so we
|
|
509
594
|
* surface it (console.error → smoke/scenario see it) and fire the `app.error`
|
|
510
595
|
* reducer(s) with `$event = PanicInfo`, exactly as §7.2.3 specifies.
|
|
@@ -621,9 +706,12 @@ function mount(app, target) {
|
|
|
621
706
|
dispatcher.dispose();
|
|
622
707
|
} };
|
|
623
708
|
}
|
|
624
|
-
function makeCapabilityRegistry(allowed) {
|
|
709
|
+
function makeCapabilityRegistry(allowed, providers) {
|
|
625
710
|
const ok = new Set(allowed);
|
|
626
|
-
return {
|
|
711
|
+
return {
|
|
712
|
+
has: (c) => ok.has(c),
|
|
713
|
+
provider: (c) => providers?.[c]
|
|
714
|
+
};
|
|
627
715
|
}
|
|
628
716
|
function makeEffectDispatcher(app, caps, onResult) {
|
|
629
717
|
const state = {
|
|
@@ -700,43 +788,50 @@ function makeEffectDispatcher(app, caps, onResult) {
|
|
|
700
788
|
};
|
|
701
789
|
}
|
|
702
790
|
function registerBuiltinEffects(app, navigate, getLive, rerender) {
|
|
791
|
+
const overridable = (cap, fn) => {
|
|
792
|
+
return async (input, caps) => {
|
|
793
|
+
const p = caps.provider(cap);
|
|
794
|
+
if (p) return p(input, caps);
|
|
795
|
+
return fn(input);
|
|
796
|
+
};
|
|
797
|
+
};
|
|
703
798
|
app.effects.navigate = {
|
|
704
799
|
name: "navigate",
|
|
705
800
|
cap: "nav.push",
|
|
706
|
-
invoke: async (input) => {
|
|
801
|
+
invoke: overridable("nav.push", async (input) => {
|
|
707
802
|
navigate(buildPath(input), false);
|
|
708
803
|
return {
|
|
709
804
|
kind: "ok",
|
|
710
805
|
value: null
|
|
711
806
|
};
|
|
712
|
-
}
|
|
807
|
+
})
|
|
713
808
|
};
|
|
714
809
|
app.effects["navigate-replace"] = {
|
|
715
810
|
name: "navigate-replace",
|
|
716
811
|
cap: "nav.replace",
|
|
717
|
-
invoke: async (input) => {
|
|
812
|
+
invoke: overridable("nav.replace", async (input) => {
|
|
718
813
|
navigate(buildPath(input), true);
|
|
719
814
|
return {
|
|
720
815
|
kind: "ok",
|
|
721
816
|
value: null
|
|
722
817
|
};
|
|
723
|
-
}
|
|
818
|
+
})
|
|
724
819
|
};
|
|
725
820
|
app.effects["navigate-back"] = {
|
|
726
821
|
name: "navigate-back",
|
|
727
822
|
cap: "nav.back",
|
|
728
|
-
invoke: async () => {
|
|
823
|
+
invoke: overridable("nav.back", async () => {
|
|
729
824
|
history.back();
|
|
730
825
|
return {
|
|
731
826
|
kind: "ok",
|
|
732
827
|
value: null
|
|
733
828
|
};
|
|
734
|
-
}
|
|
829
|
+
})
|
|
735
830
|
};
|
|
736
831
|
app.effects.toast = {
|
|
737
832
|
name: "toast",
|
|
738
833
|
cap: "notification.show",
|
|
739
|
-
invoke: async (input) => {
|
|
834
|
+
invoke: overridable("notification.show", async (input) => {
|
|
740
835
|
const t = input;
|
|
741
836
|
const banner = document.createElement("div");
|
|
742
837
|
banner.style.cssText = "position:fixed;bottom:24px;right:24px;padding:8px 16px;background:#1a1a1a;color:#fff;border-radius:8px;z-index:9999;";
|
|
@@ -747,18 +842,18 @@ function registerBuiltinEffects(app, navigate, getLive, rerender) {
|
|
|
747
842
|
kind: "ok",
|
|
748
843
|
value: null
|
|
749
844
|
};
|
|
750
|
-
}
|
|
845
|
+
})
|
|
751
846
|
};
|
|
752
847
|
app.effects.log = {
|
|
753
848
|
name: "log",
|
|
754
849
|
cap: "log.write",
|
|
755
|
-
invoke: async (input) => {
|
|
850
|
+
invoke: overridable("log.write", async (input) => {
|
|
756
851
|
console.log("[kumiki]", input);
|
|
757
852
|
return {
|
|
758
853
|
kind: "ok",
|
|
759
854
|
value: null
|
|
760
855
|
};
|
|
761
|
-
}
|
|
856
|
+
})
|
|
762
857
|
};
|
|
763
858
|
}
|
|
764
859
|
function buildPath(x) {
|
|
@@ -1215,10 +1310,8 @@ function applyResponsive(_el, raw, set) {
|
|
|
1215
1310
|
return;
|
|
1216
1311
|
}
|
|
1217
1312
|
}
|
|
1218
|
-
let animationStylesInjected = false;
|
|
1219
1313
|
function ensureAnimationStyles() {
|
|
1220
|
-
if (
|
|
1221
|
-
animationStylesInjected = true;
|
|
1314
|
+
if (findStyleNode("kumiki-animations")) return;
|
|
1222
1315
|
const css = `
|
|
1223
1316
|
@keyframes kumiki-fade { from { opacity: 0 } to { opacity: 1 } }
|
|
1224
1317
|
@keyframes kumiki-slide-up { from { transform: translateY(8px); opacity: 0 } to { transform: translateY(0); opacity: 1 } }
|
|
@@ -1234,7 +1327,7 @@ function ensureAnimationStyles() {
|
|
|
1234
1327
|
const style = document.createElement("style");
|
|
1235
1328
|
style.id = "kumiki-animations";
|
|
1236
1329
|
style.appendChild(document.createTextNode(css));
|
|
1237
|
-
|
|
1330
|
+
appendStyleNode(style);
|
|
1238
1331
|
}
|
|
1239
1332
|
function applyTransition(el, props) {
|
|
1240
1333
|
if (!props) return;
|
|
@@ -1278,15 +1371,32 @@ function motionCss(name, spec) {
|
|
|
1278
1371
|
return [`@keyframes ${cls} { from { ${from} } to { ${to} } }`, `.${cls} { animation-name: ${cls}; animation-duration: ${motionDuration(s.duration)}; animation-timing-function: ${easing}; animation-iteration-count: ${iteration}; animation-direction: ${direction}; animation-fill-mode: both; }`].join("\n");
|
|
1279
1372
|
}
|
|
1280
1373
|
/** Inject the app's motion keyframes + a `prefers-reduced-motion` guard at mount. */
|
|
1374
|
+
let currentStyleRoot = null;
|
|
1375
|
+
let currentStyleHost = null;
|
|
1376
|
+
/** Find a Kumiki style node by id within the active style root. */
|
|
1377
|
+
function findStyleNode(id) {
|
|
1378
|
+
return (currentStyleRoot ?? document).getElementById(id);
|
|
1379
|
+
}
|
|
1380
|
+
/** Append a style node to the active style root (document head, or a shadow root). */
|
|
1381
|
+
function appendStyleNode(style) {
|
|
1382
|
+
const root = currentStyleRoot ?? document;
|
|
1383
|
+
const head = root.head;
|
|
1384
|
+
if (head) head.appendChild(style);
|
|
1385
|
+
else root.appendChild(style);
|
|
1386
|
+
}
|
|
1387
|
+
/** The element that carries body-level theme styles (background/fg/font). */
|
|
1388
|
+
function styleHostEl() {
|
|
1389
|
+
return currentStyleHost ?? document.body;
|
|
1390
|
+
}
|
|
1281
1391
|
function ensureMotionStyles(app) {
|
|
1282
1392
|
const motions = app.motions ?? {};
|
|
1283
1393
|
const rules = Object.entries(motions).map(([name, spec]) => motionCss(name, spec));
|
|
1284
1394
|
rules.push(`@media (prefers-reduced-motion: reduce) { .kumiki-motion, .kumiki-anim { animation: none !important } }`);
|
|
1285
|
-
let style =
|
|
1395
|
+
let style = findStyleNode("kumiki-motions");
|
|
1286
1396
|
if (!style) {
|
|
1287
1397
|
style = document.createElement("style");
|
|
1288
1398
|
style.id = "kumiki-motions";
|
|
1289
|
-
|
|
1399
|
+
appendStyleNode(style);
|
|
1290
1400
|
}
|
|
1291
1401
|
style.textContent = rules.join("\n");
|
|
1292
1402
|
}
|
|
@@ -1313,9 +1423,12 @@ function applyStateStyles(el, props) {
|
|
|
1313
1423
|
el.dataset.kumikiState = el.dataset.kumikiState ? `${el.dataset.kumikiState} ${id}` : id;
|
|
1314
1424
|
const decls = stateStyleDecls(sub);
|
|
1315
1425
|
if (!stateStylesEl) {
|
|
1316
|
-
stateStylesEl =
|
|
1317
|
-
stateStylesEl
|
|
1318
|
-
|
|
1426
|
+
stateStylesEl = findStyleNode("kumiki-state-styles");
|
|
1427
|
+
if (!stateStylesEl) {
|
|
1428
|
+
stateStylesEl = document.createElement("style");
|
|
1429
|
+
stateStylesEl.id = "kumiki-state-styles";
|
|
1430
|
+
appendStyleNode(stateStylesEl);
|
|
1431
|
+
}
|
|
1319
1432
|
}
|
|
1320
1433
|
const selector = state === "hover" ? ":hover" : state === "focus" ? ":focus" : state === "active" ? ":active" : state === "disabled" ? ":disabled" : "[data-kumiki-selected]";
|
|
1321
1434
|
stateStylesEl.appendChild(document.createTextNode(`[data-kumiki-state~="${id}"]${selector} { ${decls} }\n`));
|
|
@@ -1351,12 +1464,13 @@ function applyThemeDefaults(app) {
|
|
|
1351
1464
|
const colors = theme.colors ?? {};
|
|
1352
1465
|
const typography = theme.typography ?? {};
|
|
1353
1466
|
const sizes = typography.size ?? {};
|
|
1354
|
-
|
|
1355
|
-
if (typeof colors.
|
|
1356
|
-
if (typeof
|
|
1357
|
-
if (typeof
|
|
1358
|
-
if (typeof
|
|
1359
|
-
|
|
1467
|
+
const host = styleHostEl();
|
|
1468
|
+
if (typeof colors.bg === "string") host.style.background = colors.bg;
|
|
1469
|
+
if (typeof colors.fg === "string") host.style.color = colors.fg;
|
|
1470
|
+
if (typeof typography.family === "string") host.style.fontFamily = typography.family;
|
|
1471
|
+
if (typeof sizes.md === "string") host.style.fontSize = sizes.md;
|
|
1472
|
+
if (typeof typography["line-height"] === "string") host.style.lineHeight = String(typography["line-height"]);
|
|
1473
|
+
const prior = findStyleNode("kumiki-theme-base");
|
|
1360
1474
|
if (prior) prior.remove();
|
|
1361
1475
|
const css = document.createElement("style");
|
|
1362
1476
|
css.id = "kumiki-theme-base";
|
|
@@ -1399,7 +1513,7 @@ function applyThemeDefaults(app) {
|
|
|
1399
1513
|
}
|
|
1400
1514
|
[data-kumiki-tile="markdown"] p { margin: 0 0 12px; }
|
|
1401
1515
|
`));
|
|
1402
|
-
|
|
1516
|
+
appendStyleNode(css);
|
|
1403
1517
|
}
|
|
1404
1518
|
function themeShadow(theme, key) {
|
|
1405
1519
|
const shadow = theme.shadow;
|
|
@@ -1816,7 +1930,7 @@ const _stdlib = {
|
|
|
1816
1930
|
};
|
|
1817
1931
|
},
|
|
1818
1932
|
/**
|
|
1819
|
-
* `.get` — the polymorphic unwrap for Option AND Result. Per spec/stdlib.md
|
|
1933
|
+
* `.get` — the polymorphic unwrap for Option AND Result. Per docs/spec/stdlib.md
|
|
1820
1934
|
* §2.2 it PANICS on the empty case (`None` / `Err`); `Some(v)` / `Ok(v)`
|
|
1821
1935
|
* unwrap to `v`. A plain (non-variant) value passes through unchanged. (Before
|
|
1822
1936
|
* v0.3 this returned the value unchanged on the empty case, so `.get` and
|
|
@@ -2094,4 +2208,4 @@ const builtinEffects = {
|
|
|
2094
2208
|
}
|
|
2095
2209
|
};
|
|
2096
2210
|
//#endregion
|
|
2097
|
-
export { KumikiPanic, _stdlib, builtinEffects, mount, runScenario, smoke };
|
|
2211
|
+
export { KumikiPanic, _stdlib, builtinEffects, defineKumikiElement, mount, runScenario, smoke };
|
package/package.json
CHANGED