@madojs/mado 0.5.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/AGENTS.md +291 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/ROADMAP.md +52 -0
- package/dist/src/component.d.ts +48 -0
- package/dist/src/component.js +140 -0
- package/dist/src/component.js.map +1 -0
- package/dist/src/context.d.ts +40 -0
- package/dist/src/context.js +67 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/css.d.ts +54 -0
- package/dist/src/css.js +137 -0
- package/dist/src/css.js.map +1 -0
- package/dist/src/devtools.d.ts +22 -0
- package/dist/src/devtools.js +63 -0
- package/dist/src/devtools.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.js +28 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/each.d.ts +39 -0
- package/dist/src/each.js +35 -0
- package/dist/src/each.js.map +1 -0
- package/dist/src/forms.d.ts +71 -0
- package/dist/src/forms.js +161 -0
- package/dist/src/forms.js.map +1 -0
- package/dist/src/head.d.ts +19 -0
- package/dist/src/head.js +97 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/html/bindings.d.ts +78 -0
- package/dist/src/html/bindings.js +304 -0
- package/dist/src/html/bindings.js.map +1 -0
- package/dist/src/html/parser.d.ts +64 -0
- package/dist/src/html/parser.js +521 -0
- package/dist/src/html/parser.js.map +1 -0
- package/dist/src/html/template-types.d.ts +27 -0
- package/dist/src/html/template-types.js +8 -0
- package/dist/src/html/template-types.js.map +1 -0
- package/dist/src/html/template.d.ts +45 -0
- package/dist/src/html/template.js +119 -0
- package/dist/src/html/template.js.map +1 -0
- package/dist/src/html.d.ts +16 -0
- package/dist/src/html.js +16 -0
- package/dist/src/html.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lazy.d.ts +38 -0
- package/dist/src/lazy.js +73 -0
- package/dist/src/lazy.js.map +1 -0
- package/dist/src/lifecycle.d.ts +45 -0
- package/dist/src/lifecycle.js +66 -0
- package/dist/src/lifecycle.js.map +1 -0
- package/dist/src/page.d.ts +161 -0
- package/dist/src/page.js +38 -0
- package/dist/src/page.js.map +1 -0
- package/dist/src/persisted.d.ts +47 -0
- package/dist/src/persisted.js +119 -0
- package/dist/src/persisted.js.map +1 -0
- package/dist/src/resource.d.ts +120 -0
- package/dist/src/resource.js +275 -0
- package/dist/src/resource.js.map +1 -0
- package/dist/src/router/manifest.d.ts +56 -0
- package/dist/src/router/manifest.js +302 -0
- package/dist/src/router/manifest.js.map +1 -0
- package/dist/src/router/match.d.ts +62 -0
- package/dist/src/router/match.js +117 -0
- package/dist/src/router/match.js.map +1 -0
- package/dist/src/router/navigation.d.ts +89 -0
- package/dist/src/router/navigation.js +263 -0
- package/dist/src/router/navigation.js.map +1 -0
- package/dist/src/router.d.ts +13 -0
- package/dist/src/router.js +13 -0
- package/dist/src/router.js.map +1 -0
- package/dist/src/signal.d.ts +67 -0
- package/dist/src/signal.js +238 -0
- package/dist/src/signal.js.map +1 -0
- package/docs/README.md +12 -0
- package/docs/en/00-the-mado-way.md +106 -0
- package/docs/en/01-routing.md +204 -0
- package/docs/en/02-project-layout.md +58 -0
- package/docs/en/03-static-bake.md +251 -0
- package/docs/en/04-ide-setup.md +162 -0
- package/docs/en/05-why-mado.md +193 -0
- package/docs/en/06-for-backenders.md +422 -0
- package/docs/en/07-llm-pitfalls.md +486 -0
- package/docs/en/08-llm-zero-history-test.md +56 -0
- package/docs/en/09-shadow-vs-light-dom.md +122 -0
- package/docs/en/README.md +16 -0
- package/docs/fr/00-the-mado-way.md +108 -0
- package/docs/fr/01-routing.md +202 -0
- package/docs/fr/02-project-layout.md +58 -0
- package/docs/fr/03-static-bake.md +290 -0
- package/docs/fr/04-ide-setup.md +162 -0
- package/docs/fr/05-why-mado.md +193 -0
- package/docs/fr/06-for-backenders.md +432 -0
- package/docs/fr/07-llm-pitfalls.md +487 -0
- package/docs/fr/08-llm-zero-history-test.md +60 -0
- package/docs/fr/09-shadow-vs-light-dom.md +121 -0
- package/docs/fr/README.md +16 -0
- package/docs/ru/00-the-mado-way.md +93 -0
- package/docs/ru/01-routing.md +194 -0
- package/docs/ru/02-project-layout.md +57 -0
- package/docs/ru/03-static-bake.md +251 -0
- package/docs/ru/04-ide-setup.md +144 -0
- package/docs/ru/05-why-mado.md +193 -0
- package/docs/ru/06-for-backenders.md +422 -0
- package/docs/ru/07-llm-pitfalls.md +485 -0
- package/docs/ru/08-llm-zero-history-test.md +56 -0
- package/docs/ru/09-shadow-vs-light-dom.md +122 -0
- package/docs/ru/README.md +14 -0
- package/docs/uk/00-the-mado-way.md +54 -0
- package/docs/uk/01-routing.md +82 -0
- package/docs/uk/02-project-layout.md +46 -0
- package/docs/uk/03-static-bake.md +49 -0
- package/docs/uk/04-ide-setup.md +26 -0
- package/docs/uk/05-why-mado.md +34 -0
- package/docs/uk/06-for-backenders.md +50 -0
- package/docs/uk/07-llm-pitfalls.md +82 -0
- package/docs/uk/08-llm-zero-history-test.md +31 -0
- package/docs/uk/09-shadow-vs-light-dom.md +40 -0
- package/docs/uk/README.md +16 -0
- package/llms.txt +155 -0
- package/package.json +81 -0
- package/scripts/bake.mjs +406 -0
- package/scripts/bundle.mjs +146 -0
- package/scripts/cli.mjs +382 -0
- package/scripts/new.mjs +80 -0
- package/scripts/preview.mjs +176 -0
- package/scripts/release-notes.mjs +66 -0
- package/scripts/showcase-regression.mjs +392 -0
- package/server/serve.mjs +292 -0
- package/starters/crud/README.md +21 -0
- package/starters/crud/index.html +20 -0
- package/starters/crud/package.json +17 -0
- package/starters/crud/src/components/app-shell.ts +51 -0
- package/starters/crud/src/components/ticket-detail.ts +33 -0
- package/starters/crud/src/components/ticket-form.ts +69 -0
- package/starters/crud/src/components/ticket-list.ts +66 -0
- package/starters/crud/src/lib/api.ts +76 -0
- package/starters/crud/src/main.ts +12 -0
- package/starters/crud/src/pages/home.ts +18 -0
- package/starters/crud/src/pages/not-found.ts +12 -0
- package/starters/crud/src/pages/ticket-detail.ts +6 -0
- package/starters/crud/src/pages/ticket-new.ts +6 -0
- package/starters/crud/src/pages/tickets.ts +6 -0
- package/starters/crud/src/routes.ts +9 -0
- package/starters/crud/src/styles/global.ts +155 -0
- package/starters/crud/tsconfig.json +15 -0
- package/starters/minimal/README.md +19 -0
- package/starters/minimal/index.html +20 -0
- package/starters/minimal/package.json +17 -0
- package/starters/minimal/src/components/app-counter.ts +31 -0
- package/starters/minimal/src/main.ts +9 -0
- package/starters/minimal/src/pages/home.ts +18 -0
- package/starters/minimal/src/pages/not-found.ts +14 -0
- package/starters/minimal/src/routes.ts +6 -0
- package/starters/minimal/src/styles/global.ts +60 -0
- package/starters/minimal/tsconfig.json +15 -0
- package/templates/page-detail.ts +63 -0
- package/templates/page-form.ts +94 -0
- package/templates/page-list.ts +79 -0
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Mado is pre-v1. The goal is not to grow sideways, but to keep the runtime small,
|
|
4
|
+
readable and reliable under real application pressure.
|
|
5
|
+
|
|
6
|
+
## Current Focus
|
|
7
|
+
|
|
8
|
+
- Keep the public runtime API small: `html`, `component`, `routes`, `resource`,
|
|
9
|
+
`mutation`, `useForm`, `each`, `createContext`, `persisted`, `lazy`.
|
|
10
|
+
- Use real apps and examples as pressure tests before adding primitives.
|
|
11
|
+
- Keep runtime dependencies at zero.
|
|
12
|
+
- Keep English as the public/default project language, with localized docs in
|
|
13
|
+
`docs/ru`, `docs/fr` and `docs/uk`.
|
|
14
|
+
|
|
15
|
+
## Before v1
|
|
16
|
+
|
|
17
|
+
- Browser compatibility pass across current Chrome, Edge, Firefox and Safari.
|
|
18
|
+
- Accessibility pass for examples and common component patterns.
|
|
19
|
+
- Public API audit: names, warnings, lifecycle rules, docs coverage.
|
|
20
|
+
- Release hygiene: npm provenance, GitHub repo metadata, tags and changelog.
|
|
21
|
+
- Size reporting command or CI summary with ESM and bundled/minified budgets.
|
|
22
|
+
- Real commercial app test: validate auth, forms, tables, resources, route
|
|
23
|
+
transitions and long-lived sessions outside toy examples.
|
|
24
|
+
|
|
25
|
+
## Explicitly Out Of Scope For Now
|
|
26
|
+
|
|
27
|
+
- Runtime dependencies.
|
|
28
|
+
- UI kit / component library.
|
|
29
|
+
- SSR with hydration.
|
|
30
|
+
- Large props API.
|
|
31
|
+
- Framework-specific build tool.
|
|
32
|
+
- Plugin ecosystem before the core is stable.
|
|
33
|
+
|
|
34
|
+
## Release History
|
|
35
|
+
|
|
36
|
+
| Date | Milestone | Notes |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| 2026-06-03 | Core stabilization | Parser rewritten as a state machine; keyed `each()` reconciliation; lifecycle-aware `resource()`; router isolation; lazy `computed()`; regression tests added. |
|
|
39
|
+
| 2026-06-03 | Documentation foundation | Honest README, routing docs, static bake docs, IDE setup, “Why Mado”, backend mental model and LLM pitfalls. |
|
|
40
|
+
| 2026-06-03 | Publish readiness | Package metadata, exports, MIT license, contributing guide, GitHub templates and CI scaffolding. |
|
|
41
|
+
| 2026-06-03 | Cloudflare prerender PoC | Edge prerender example for SEO without SSR hydration. |
|
|
42
|
+
| 2026-06-03 | AI-ready files | `AGENTS.md`, `llms.txt`, Copilot instructions and LLM-specific pitfalls. |
|
|
43
|
+
| 2026-06-03 | v0.3 hardening | Nested template cleanup, stale async route guard, Shadow DOM link/prefetch tests, scroll behavior, `warnOnce`, component reconnect/style tests. |
|
|
44
|
+
| 2026-06-05 | v0.4 showcase max | `examples/showcase` became a SaaS CRM pressure app with accounts, deals, activity, nested routes, context services and browser regression. |
|
|
45
|
+
| 2026-06-06 | v0.5 project shape | Unified `mado` CLI, dev server logs, docs language skeleton, examples cleanup (`basic`, `tickets`, `showcase`, `cloudflare`). |
|
|
46
|
+
| 2026-06-06 | Mado rebrand | Public package/import name `madojs`, CLI `mado`, brand/docs/examples updated, internal legacy markers cleaned. |
|
|
47
|
+
| 2026-06-06 | Public polish | English public surface, localized docs, translated code comments/examples/templates/GitHub files. |
|
|
48
|
+
|
|
49
|
+
## Future Ideas
|
|
50
|
+
|
|
51
|
+
Ideas live in `TODO.md`. They are not commitments until a real app or issue
|
|
52
|
+
proves the need.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around Custom Elements.
|
|
3
|
+
*
|
|
4
|
+
* component('x-counter', () => {
|
|
5
|
+
* const count = signal(0);
|
|
6
|
+
* return () => html`<button @click=${() => count.update(n=>n+1)}>${count}</button>`;
|
|
7
|
+
* }, {
|
|
8
|
+
* styles: css`button { padding: .5rem }`,
|
|
9
|
+
* });
|
|
10
|
+
*
|
|
11
|
+
* The setup function is called once on the first connectedCallback.
|
|
12
|
+
* The returned render function is called via an effect, so any signals
|
|
13
|
+
* read inside it automatically re-render the template.
|
|
14
|
+
*
|
|
15
|
+
* Shadow DOM (open) is used by default. It can be disabled, and
|
|
16
|
+
* styles will be scoped via @scope (or a tag-prefix fallback).
|
|
17
|
+
*/
|
|
18
|
+
import { type Disposer } from "./signal.js";
|
|
19
|
+
import { html, type TemplateResult } from "./html.js";
|
|
20
|
+
import { type CSSResult } from "./css.js";
|
|
21
|
+
export interface ComponentContext {
|
|
22
|
+
host: HTMLElement;
|
|
23
|
+
/** Run cleanup when the component is removed. */
|
|
24
|
+
onDispose(fn: Disposer): void;
|
|
25
|
+
}
|
|
26
|
+
export type SetupFn = (ctx: ComponentContext) => () => TemplateResult;
|
|
27
|
+
export type StyleInput = string | CSSResult | Array<string | CSSResult>;
|
|
28
|
+
export interface ComponentOptions {
|
|
29
|
+
/** Enable Shadow DOM (default: true). */
|
|
30
|
+
shadow?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Component styles. Accepts:
|
|
33
|
+
* - a CSS string (quick start)
|
|
34
|
+
* - a CSSStyleSheet via `css\`...\`` (recommended — one copy in memory)
|
|
35
|
+
* - an array of the above
|
|
36
|
+
*/
|
|
37
|
+
styles?: StyleInput;
|
|
38
|
+
/**
|
|
39
|
+
* List of observed attributes.
|
|
40
|
+
*
|
|
41
|
+
* v0.3: this is plain reflection into host[attr], without a reactive props API.
|
|
42
|
+
* If you need to re-render the component when an attribute changes, create a signal()
|
|
43
|
+
* inside setup() and update it manually from your own wrapper/event.
|
|
44
|
+
*/
|
|
45
|
+
observedAttributes?: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
export declare function component(tagName: string, setup: SetupFn, options?: ComponentOptions): void;
|
|
48
|
+
export { html };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around Custom Elements.
|
|
3
|
+
*
|
|
4
|
+
* component('x-counter', () => {
|
|
5
|
+
* const count = signal(0);
|
|
6
|
+
* return () => html`<button @click=${() => count.update(n=>n+1)}>${count}</button>`;
|
|
7
|
+
* }, {
|
|
8
|
+
* styles: css`button { padding: .5rem }`,
|
|
9
|
+
* });
|
|
10
|
+
*
|
|
11
|
+
* The setup function is called once on the first connectedCallback.
|
|
12
|
+
* The returned render function is called via an effect, so any signals
|
|
13
|
+
* read inside it automatically re-render the template.
|
|
14
|
+
*
|
|
15
|
+
* Shadow DOM (open) is used by default. It can be disabled, and
|
|
16
|
+
* styles will be scoped via @scope (or a tag-prefix fallback).
|
|
17
|
+
*/
|
|
18
|
+
import { effect } from "./signal.js";
|
|
19
|
+
import { html, render } from "./html.js";
|
|
20
|
+
import { adopt, scopeStyles } from "./css.js";
|
|
21
|
+
import { createLifecycle, runInLifecycle } from "./lifecycle.js";
|
|
22
|
+
import { warnOnce } from "./diagnostics.js";
|
|
23
|
+
export function component(tagName, setup, options = {}) {
|
|
24
|
+
if (!tagName.includes("-")) {
|
|
25
|
+
warnOnce(`component-invalid-tag-${tagName}`, `component("${tagName}") skipped: Custom Element names must contain a hyphen.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const existingMeta = registered.get(tagName);
|
|
29
|
+
if (customElements.get(tagName)) {
|
|
30
|
+
if (!existingMeta ||
|
|
31
|
+
existingMeta.setup !== setup ||
|
|
32
|
+
!sameComponentOptions(existingMeta.options, options)) {
|
|
33
|
+
warnOnce(`component-duplicate-${tagName}`, `component("${tagName}") is already registered. Re-registration with different setup/options is ignored.`);
|
|
34
|
+
}
|
|
35
|
+
return; // idempotent hot-reload
|
|
36
|
+
}
|
|
37
|
+
const useShadow = options.shadow !== false;
|
|
38
|
+
const observed = options.observedAttributes ?? [];
|
|
39
|
+
// Normalize styles to an array of CSSStyleSheet once.
|
|
40
|
+
// Sheets are shared across all instances — memory is not duplicated.
|
|
41
|
+
const stylesheets = normalizeStyles(options.styles, tagName, useShadow);
|
|
42
|
+
class MadoElement extends HTMLElement {
|
|
43
|
+
static get observedAttributes() {
|
|
44
|
+
return [...observed];
|
|
45
|
+
}
|
|
46
|
+
#root;
|
|
47
|
+
#renderer = null;
|
|
48
|
+
#effectDispose = null;
|
|
49
|
+
#lifecycle = null;
|
|
50
|
+
#connected = false;
|
|
51
|
+
constructor() {
|
|
52
|
+
super();
|
|
53
|
+
this.#root = useShadow ? this.attachShadow({ mode: "open" }) : this;
|
|
54
|
+
}
|
|
55
|
+
connectedCallback() {
|
|
56
|
+
if (this.#connected)
|
|
57
|
+
return;
|
|
58
|
+
this.#connected = true;
|
|
59
|
+
if (stylesheets.length > 0) {
|
|
60
|
+
if (useShadow) {
|
|
61
|
+
adopt(this.#root, ...stylesheets);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
installGlobalSheets(stylesheets);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// create a lifecycle for this component. Any function
|
|
68
|
+
// called from setup() (resource, ...) will see it via
|
|
69
|
+
// getCurrentLifecycle() and register its own cleanup.
|
|
70
|
+
const lifecycle = createLifecycle();
|
|
71
|
+
this.#lifecycle = lifecycle;
|
|
72
|
+
const ctx = {
|
|
73
|
+
host: this,
|
|
74
|
+
// ctx.onDispose proxies to lifecycle — the single source of truth
|
|
75
|
+
// for component cleanups (including auto-cleanup from
|
|
76
|
+
// resource(), navigator listeners, etc.).
|
|
77
|
+
onDispose: (fn) => lifecycle.onDispose(fn),
|
|
78
|
+
};
|
|
79
|
+
this.#renderer = runInLifecycle(lifecycle, () => setup(ctx));
|
|
80
|
+
this.#effectDispose = effect(() => {
|
|
81
|
+
render(this.#renderer(), this.#root);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
disconnectedCallback() {
|
|
85
|
+
this.#effectDispose?.();
|
|
86
|
+
this.#effectDispose = null;
|
|
87
|
+
this.#lifecycle?.dispose();
|
|
88
|
+
this.#lifecycle = null;
|
|
89
|
+
this.#connected = false;
|
|
90
|
+
}
|
|
91
|
+
attributeChangedCallback(name, _old, value) {
|
|
92
|
+
// reflect attribute to property — user can bind a signal to it
|
|
93
|
+
this[name] = value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
customElements.define(tagName, MadoElement);
|
|
97
|
+
registered.set(tagName, { setup, options });
|
|
98
|
+
}
|
|
99
|
+
// ---------- helpers ----------
|
|
100
|
+
function normalizeStyles(input, tagName, useShadow) {
|
|
101
|
+
if (!input)
|
|
102
|
+
return [];
|
|
103
|
+
const arr = Array.isArray(input) ? input : [input];
|
|
104
|
+
return arr.map((s) => {
|
|
105
|
+
let sheet;
|
|
106
|
+
if (typeof s === "string") {
|
|
107
|
+
sheet = new CSSStyleSheet();
|
|
108
|
+
sheet.replaceSync(s);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
sheet = s;
|
|
112
|
+
}
|
|
113
|
+
// light DOM: scope by tag name
|
|
114
|
+
return useShadow ? sheet : scopeStyles(tagName, sheet);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const installedGlobal = new WeakSet();
|
|
118
|
+
const registered = new Map();
|
|
119
|
+
function sameComponentOptions(a, b) {
|
|
120
|
+
if (a.shadow !== b.shadow)
|
|
121
|
+
return false;
|
|
122
|
+
if (a.styles !== b.styles)
|
|
123
|
+
return false;
|
|
124
|
+
const aa = a.observedAttributes ?? [];
|
|
125
|
+
const bb = b.observedAttributes ?? [];
|
|
126
|
+
if (aa.length !== bb.length)
|
|
127
|
+
return false;
|
|
128
|
+
return aa.every((name, i) => name === bb[i]);
|
|
129
|
+
}
|
|
130
|
+
function installGlobalSheets(sheets) {
|
|
131
|
+
const toAdd = sheets.filter((s) => !installedGlobal.has(s));
|
|
132
|
+
if (toAdd.length === 0)
|
|
133
|
+
return;
|
|
134
|
+
for (const s of toAdd)
|
|
135
|
+
installedGlobal.add(s);
|
|
136
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ...toAdd];
|
|
137
|
+
}
|
|
138
|
+
// Convenience re-export.
|
|
139
|
+
export { html };
|
|
140
|
+
//# sourceMappingURL=component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context (DI) without props drilling. A native pattern from Lit / Open Web Components.
|
|
3
|
+
*
|
|
4
|
+
* const ThemeCtx = createContext<'light'|'dark'>('light');
|
|
5
|
+
*
|
|
6
|
+
* component('x-app', ({ host }) => {
|
|
7
|
+
* provide(host, ThemeCtx, signal('dark'));
|
|
8
|
+
* return () => html`<x-child/>`;
|
|
9
|
+
* });
|
|
10
|
+
*
|
|
11
|
+
* component('x-child', ({ host }) => {
|
|
12
|
+
* const theme = inject(host, ThemeCtx); // signal: () => 'dark' | 'light'
|
|
13
|
+
* return () => html`<div data-theme=${theme}>...</div>`;
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* How it works:
|
|
17
|
+
* provide() listens for the 'mado:context' event on the host — when a child
|
|
18
|
+
* component dispatches it (bubbles), the parent writes the current signal
|
|
19
|
+
* into the event detail and calls preventDefault.
|
|
20
|
+
* inject() dispatches the event and reads the result.
|
|
21
|
+
* This fully conforms to the Web Components Community Context Protocol.
|
|
22
|
+
*/
|
|
23
|
+
import { type Signal } from "./signal.js";
|
|
24
|
+
export interface Context<T> {
|
|
25
|
+
readonly _ctx: true;
|
|
26
|
+
readonly key: symbol;
|
|
27
|
+
readonly defaultValue: T;
|
|
28
|
+
}
|
|
29
|
+
export declare function createContext<T>(defaultValue: T): Context<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Declare that this host provides a value for the given context.
|
|
32
|
+
* Returns the signal itself so the provider can update it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function provide<T>(host: HTMLElement, ctx: Context<T>, initial: T | Signal<T>): Signal<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Request a context value. Walks up the DOM tree (including
|
|
37
|
+
* Shadow DOM via composed) to the first provider. If not found —
|
|
38
|
+
* returns the defaultValue wrapped in a signal.
|
|
39
|
+
*/
|
|
40
|
+
export declare function inject<T>(host: HTMLElement, ctx: Context<T>): Signal<T>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context (DI) without props drilling. A native pattern from Lit / Open Web Components.
|
|
3
|
+
*
|
|
4
|
+
* const ThemeCtx = createContext<'light'|'dark'>('light');
|
|
5
|
+
*
|
|
6
|
+
* component('x-app', ({ host }) => {
|
|
7
|
+
* provide(host, ThemeCtx, signal('dark'));
|
|
8
|
+
* return () => html`<x-child/>`;
|
|
9
|
+
* });
|
|
10
|
+
*
|
|
11
|
+
* component('x-child', ({ host }) => {
|
|
12
|
+
* const theme = inject(host, ThemeCtx); // signal: () => 'dark' | 'light'
|
|
13
|
+
* return () => html`<div data-theme=${theme}>...</div>`;
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* How it works:
|
|
17
|
+
* provide() listens for the 'mado:context' event on the host — when a child
|
|
18
|
+
* component dispatches it (bubbles), the parent writes the current signal
|
|
19
|
+
* into the event detail and calls preventDefault.
|
|
20
|
+
* inject() dispatches the event and reads the result.
|
|
21
|
+
* This fully conforms to the Web Components Community Context Protocol.
|
|
22
|
+
*/
|
|
23
|
+
import { signal } from "./signal.js";
|
|
24
|
+
const CONTEXT_EVENT = "mado:context";
|
|
25
|
+
export function createContext(defaultValue) {
|
|
26
|
+
return {
|
|
27
|
+
_ctx: true,
|
|
28
|
+
key: Symbol("madoctx"),
|
|
29
|
+
defaultValue,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Declare that this host provides a value for the given context.
|
|
34
|
+
* Returns the signal itself so the provider can update it.
|
|
35
|
+
*/
|
|
36
|
+
export function provide(host, ctx, initial) {
|
|
37
|
+
const sig = typeof initial === "function"
|
|
38
|
+
? initial
|
|
39
|
+
: signal(initial);
|
|
40
|
+
const handler = (e) => {
|
|
41
|
+
const ce = e;
|
|
42
|
+
if (ce.detail.key !== ctx.key)
|
|
43
|
+
return;
|
|
44
|
+
ce.detail.value = sig;
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
};
|
|
47
|
+
host.addEventListener(CONTEXT_EVENT, handler);
|
|
48
|
+
return sig;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Request a context value. Walks up the DOM tree (including
|
|
52
|
+
* Shadow DOM via composed) to the first provider. If not found —
|
|
53
|
+
* returns the defaultValue wrapped in a signal.
|
|
54
|
+
*/
|
|
55
|
+
export function inject(host, ctx) {
|
|
56
|
+
const detail = { key: ctx.key };
|
|
57
|
+
host.dispatchEvent(new CustomEvent(CONTEXT_EVENT, {
|
|
58
|
+
detail,
|
|
59
|
+
bubbles: true,
|
|
60
|
+
composed: true,
|
|
61
|
+
cancelable: true,
|
|
62
|
+
}));
|
|
63
|
+
if (detail.value)
|
|
64
|
+
return detail.value;
|
|
65
|
+
return signal(ctx.defaultValue);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,MAAM,EAAe,MAAM,aAAa,CAAC;AAElD,MAAM,aAAa,GAAG,cAAc,CAAC;AAQrC,MAAM,UAAU,aAAa,CAAI,YAAe;IAC9C,OAAO;QACL,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC;QACtB,YAAY;KACb,CAAC;AACJ,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,OAAO,CACrB,IAAiB,EACjB,GAAe,EACf,OAAsB;IAEtB,MAAM,GAAG,GACP,OAAO,OAAO,KAAK,UAAU;QAC3B,CAAC,CAAE,OAAqB;QACxB,CAAC,CAAC,MAAM,CAAC,OAAY,CAAC,CAAC;IAE3B,MAAM,OAAO,GAAG,CAAC,CAAQ,EAAE,EAAE;QAC3B,MAAM,EAAE,GAAG,CAAoC,CAAC;QAChD,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG;YAAE,OAAO;QACtC,EAAE,CAAC,MAAM,CAAC,KAAK,GAAG,GAAsB,CAAC;QACzC,CAAC,CAAC,eAAe,EAAE,CAAC;IACtB,CAAC,CAAC;IACF,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAE9C,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAI,IAAiB,EAAE,GAAe;IAC1D,MAAM,MAAM,GAAuB,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;IACpD,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,aAAa,EAAE;QAC7B,MAAM;QACN,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;KACjB,CAAC,CACH,CAAC;IACF,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,KAAkB,CAAC;IACnD,OAAO,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styles without CSS-in-JS, but ergonomic.
|
|
3
|
+
*
|
|
4
|
+
* Idea:
|
|
5
|
+
* 1. `css\`...\`` — tagged literal, returns a CSSStyleSheet (Constructable Stylesheet).
|
|
6
|
+
* 2. Sheet is shared across all component instances (one copy in memory).
|
|
7
|
+
* 3. Component applies the sheet via `shadowRoot.adoptedStyleSheets`.
|
|
8
|
+
* 4. No runtime CSS parsers, no className hashes —
|
|
9
|
+
* the browser does all the work.
|
|
10
|
+
*
|
|
11
|
+
* Theming:
|
|
12
|
+
* - change CSS variables on :host or :root — no re-renders needed.
|
|
13
|
+
* - `cssVars({ '--accent': color })` → ready string for style="...".
|
|
14
|
+
*
|
|
15
|
+
* Optional scope without Shadow DOM:
|
|
16
|
+
* - if the browser has @scope (Chrome 118+, Safari 17.4+), we wrap styles.
|
|
17
|
+
* - fallback: prefix selectors with the tag — a simple regex at string level.
|
|
18
|
+
*/
|
|
19
|
+
export type CSSResult = CSSStyleSheet;
|
|
20
|
+
/**
|
|
21
|
+
* Tagged literal for CSS. Returns a CSSStyleSheet ready for
|
|
22
|
+
* adoptedStyleSheets. Value interpolation — only primitives or
|
|
23
|
+
* other CSSResult (for composition).
|
|
24
|
+
*
|
|
25
|
+
* Injection guard: strings with `<` or `>` are forbidden so that
|
|
26
|
+
* script tags cannot accidentally be injected through styles.
|
|
27
|
+
*/
|
|
28
|
+
export declare function css(strings: TemplateStringsArray, ...values: unknown[]): CSSResult;
|
|
29
|
+
/**
|
|
30
|
+
* Build an inline-style string from a CSS variables object.
|
|
31
|
+
*
|
|
32
|
+
* cssVars({ '--accent': '#f00', '--pad': '1rem' })
|
|
33
|
+
* → '--accent: #f00; --pad: 1rem;'
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* html`<div style=${cssVars({ '--accent': color })}>...</div>`
|
|
37
|
+
* html`<x-app style=${cssVars(theme())}>...</x-app>` // signal — auto-update
|
|
38
|
+
*/
|
|
39
|
+
export declare function cssVars(vars: Record<string, string | number>): string;
|
|
40
|
+
/**
|
|
41
|
+
* Apply sheets to a ShadowRoot. Idempotent: the same sheet
|
|
42
|
+
* can be adopted into dozens of components without duplicating styles.
|
|
43
|
+
*/
|
|
44
|
+
export declare function adopt(root: ShadowRoot, ...sheets: CSSResult[]): void;
|
|
45
|
+
/**
|
|
46
|
+
* Build scoped style text limited to a selector (for light DOM).
|
|
47
|
+
* Uses native @scope if the browser supports it.
|
|
48
|
+
* Otherwise — naive selector prefixing.
|
|
49
|
+
*
|
|
50
|
+
* scopeStyles('x-button', 'button { color: red }')
|
|
51
|
+
* → '@scope (x-button) { button { color: red } }' // or
|
|
52
|
+
* → 'x-button button { color: red }'
|
|
53
|
+
*/
|
|
54
|
+
export declare function scopeStyles(tagName: string, sheet: CSSResult): CSSResult;
|
package/dist/src/css.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styles without CSS-in-JS, but ergonomic.
|
|
3
|
+
*
|
|
4
|
+
* Idea:
|
|
5
|
+
* 1. `css\`...\`` — tagged literal, returns a CSSStyleSheet (Constructable Stylesheet).
|
|
6
|
+
* 2. Sheet is shared across all component instances (one copy in memory).
|
|
7
|
+
* 3. Component applies the sheet via `shadowRoot.adoptedStyleSheets`.
|
|
8
|
+
* 4. No runtime CSS parsers, no className hashes —
|
|
9
|
+
* the browser does all the work.
|
|
10
|
+
*
|
|
11
|
+
* Theming:
|
|
12
|
+
* - change CSS variables on :host or :root — no re-renders needed.
|
|
13
|
+
* - `cssVars({ '--accent': color })` → ready string for style="...".
|
|
14
|
+
*
|
|
15
|
+
* Optional scope without Shadow DOM:
|
|
16
|
+
* - if the browser has @scope (Chrome 118+, Safari 17.4+), we wrap styles.
|
|
17
|
+
* - fallback: prefix selectors with the tag — a simple regex at string level.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Tagged literal for CSS. Returns a CSSStyleSheet ready for
|
|
21
|
+
* adoptedStyleSheets. Value interpolation — only primitives or
|
|
22
|
+
* other CSSResult (for composition).
|
|
23
|
+
*
|
|
24
|
+
* Injection guard: strings with `<` or `>` are forbidden so that
|
|
25
|
+
* script tags cannot accidentally be injected through styles.
|
|
26
|
+
*/
|
|
27
|
+
export function css(strings, ...values) {
|
|
28
|
+
let text = "";
|
|
29
|
+
for (let i = 0; i < strings.length; i++) {
|
|
30
|
+
text += strings[i];
|
|
31
|
+
if (i < strings.length - 1) {
|
|
32
|
+
const v = values[i];
|
|
33
|
+
if (v == null)
|
|
34
|
+
continue;
|
|
35
|
+
if (v instanceof CSSStyleSheet) {
|
|
36
|
+
// composition — insert all rules
|
|
37
|
+
for (const rule of v.cssRules)
|
|
38
|
+
text += rule.cssText;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const s = String(v);
|
|
42
|
+
if (/[<>]/.test(s)) {
|
|
43
|
+
throw new Error("css``: `<` and `>` are forbidden in interpolations");
|
|
44
|
+
}
|
|
45
|
+
text += s;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const sheet = new CSSStyleSheet();
|
|
49
|
+
sheet.replaceSync(text);
|
|
50
|
+
return sheet;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build an inline-style string from a CSS variables object.
|
|
54
|
+
*
|
|
55
|
+
* cssVars({ '--accent': '#f00', '--pad': '1rem' })
|
|
56
|
+
* → '--accent: #f00; --pad: 1rem;'
|
|
57
|
+
*
|
|
58
|
+
* Usage:
|
|
59
|
+
* html`<div style=${cssVars({ '--accent': color })}>...</div>`
|
|
60
|
+
* html`<x-app style=${cssVars(theme())}>...</x-app>` // signal — auto-update
|
|
61
|
+
*/
|
|
62
|
+
export function cssVars(vars) {
|
|
63
|
+
let out = "";
|
|
64
|
+
for (const k in vars) {
|
|
65
|
+
const v = vars[k];
|
|
66
|
+
if (v == null)
|
|
67
|
+
continue;
|
|
68
|
+
const name = k.startsWith("--") ? k : `--${k}`;
|
|
69
|
+
out += `${name}:${v};`;
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Apply sheets to a ShadowRoot. Idempotent: the same sheet
|
|
75
|
+
* can be adopted into dozens of components without duplicating styles.
|
|
76
|
+
*/
|
|
77
|
+
export function adopt(root, ...sheets) {
|
|
78
|
+
// append rather than overwrite — user may have already adopted something
|
|
79
|
+
const existing = root.adoptedStyleSheets;
|
|
80
|
+
const toAdd = sheets.filter((s) => !existing.includes(s));
|
|
81
|
+
if (toAdd.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
root.adoptedStyleSheets = [...existing, ...toAdd];
|
|
84
|
+
}
|
|
85
|
+
// ---------- Scope for light DOM (without Shadow DOM) ----------
|
|
86
|
+
const hasScope = (() => {
|
|
87
|
+
try {
|
|
88
|
+
return CSS.supports("selector(:scope)") &&
|
|
89
|
+
// @scope at-rule support check
|
|
90
|
+
typeof CSSRule.SCOPE_RULE !==
|
|
91
|
+
"undefined";
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
/**
|
|
98
|
+
* Build scoped style text limited to a selector (for light DOM).
|
|
99
|
+
* Uses native @scope if the browser supports it.
|
|
100
|
+
* Otherwise — naive selector prefixing.
|
|
101
|
+
*
|
|
102
|
+
* scopeStyles('x-button', 'button { color: red }')
|
|
103
|
+
* → '@scope (x-button) { button { color: red } }' // or
|
|
104
|
+
* → 'x-button button { color: red }'
|
|
105
|
+
*/
|
|
106
|
+
export function scopeStyles(tagName, sheet) {
|
|
107
|
+
let text = "";
|
|
108
|
+
for (const rule of sheet.cssRules)
|
|
109
|
+
text += rule.cssText;
|
|
110
|
+
let scoped;
|
|
111
|
+
if (hasScope) {
|
|
112
|
+
scoped = `@scope (${tagName}) { ${text} }`;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// naive: prefix every top-level selector with `tagName `.
|
|
116
|
+
// Works for simple cases "button {...}", "p, span {...}".
|
|
117
|
+
// Does not touch @-rules.
|
|
118
|
+
scoped = text.replace(/(^|\})\s*([^{}@][^{}]*)\{/g, (_m, brace, sel) => {
|
|
119
|
+
const prefixed = sel
|
|
120
|
+
.split(",")
|
|
121
|
+
.map((s) => {
|
|
122
|
+
const trimmed = s.trim();
|
|
123
|
+
if (!trimmed)
|
|
124
|
+
return trimmed;
|
|
125
|
+
if (trimmed.startsWith(tagName))
|
|
126
|
+
return trimmed;
|
|
127
|
+
return `${tagName} ${trimmed}`;
|
|
128
|
+
})
|
|
129
|
+
.join(", ");
|
|
130
|
+
return `${brace} ${prefixed} {`;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const out = new CSSStyleSheet();
|
|
134
|
+
out.replaceSync(scoped);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/css.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev logger for signals and effects.
|
|
3
|
+
*
|
|
4
|
+
* Enabled by a flag in localStorage:
|
|
5
|
+
* localStorage.madoDebug = '1'
|
|
6
|
+
*
|
|
7
|
+
* What it does:
|
|
8
|
+
* - wraps signal/effect/computed for logging
|
|
9
|
+
* - shows [signal] name: old → new
|
|
10
|
+
* - groups effects via console.group / console.groupEnd
|
|
11
|
+
*
|
|
12
|
+
* Setup:
|
|
13
|
+
* import '@madojs/mado/devtools.js'; // at the very top of app.ts
|
|
14
|
+
*
|
|
15
|
+
* In production — just don't import this file, and all dev code will be
|
|
16
|
+
* tree-shaken (or simply absent from the module graph).
|
|
17
|
+
*
|
|
18
|
+
* Implementation: imports the already-initialised signal module and
|
|
19
|
+
* patches the factory. A simple proof-of-concept approach; for production-
|
|
20
|
+
* grade DevTools a full hook is needed, but that's a different story.
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev logger for signals and effects.
|
|
3
|
+
*
|
|
4
|
+
* Enabled by a flag in localStorage:
|
|
5
|
+
* localStorage.madoDebug = '1'
|
|
6
|
+
*
|
|
7
|
+
* What it does:
|
|
8
|
+
* - wraps signal/effect/computed for logging
|
|
9
|
+
* - shows [signal] name: old → new
|
|
10
|
+
* - groups effects via console.group / console.groupEnd
|
|
11
|
+
*
|
|
12
|
+
* Setup:
|
|
13
|
+
* import '@madojs/mado/devtools.js'; // at the very top of app.ts
|
|
14
|
+
*
|
|
15
|
+
* In production — just don't import this file, and all dev code will be
|
|
16
|
+
* tree-shaken (or simply absent from the module graph).
|
|
17
|
+
*
|
|
18
|
+
* Implementation: imports the already-initialised signal module and
|
|
19
|
+
* patches the factory. A simple proof-of-concept approach; for production-
|
|
20
|
+
* grade DevTools a full hook is needed, but that's a different story.
|
|
21
|
+
*/
|
|
22
|
+
import * as signalModule from "./signal.js";
|
|
23
|
+
const ENABLED = typeof localStorage !== "undefined" &&
|
|
24
|
+
localStorage.getItem("madoDebug") === "1";
|
|
25
|
+
if (ENABLED) {
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
console.info("%c[mado] devtools enabled", "color: #888");
|
|
28
|
+
const origSignal = signalModule.signal;
|
|
29
|
+
let counter = 0;
|
|
30
|
+
// @ts-expect-error patching module live
|
|
31
|
+
signalModule.signal = function patchedSignal(initial) {
|
|
32
|
+
const name = `s${++counter}`;
|
|
33
|
+
const sig = origSignal(initial);
|
|
34
|
+
const origSet = sig.set;
|
|
35
|
+
sig.set = (next) => {
|
|
36
|
+
const prev = sig.peek();
|
|
37
|
+
if (!Object.is(prev, next)) {
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.log(`%c[signal ${name}]`, "color: #888", prev, "→", next);
|
|
40
|
+
}
|
|
41
|
+
origSet(next);
|
|
42
|
+
};
|
|
43
|
+
return sig;
|
|
44
|
+
};
|
|
45
|
+
const origEffect = signalModule.effect;
|
|
46
|
+
// @ts-expect-error patching module live
|
|
47
|
+
signalModule.effect = function patchedEffect(fn) {
|
|
48
|
+
let id = 0;
|
|
49
|
+
return origEffect(() => {
|
|
50
|
+
const tag = `[effect #${++id}]`;
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.groupCollapsed(tag);
|
|
53
|
+
try {
|
|
54
|
+
return fn();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.groupEnd();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=devtools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../src/devtools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,YAAY,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GACX,OAAO,YAAY,KAAK,WAAW;IACnC,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC;AAE5C,IAAI,OAAO,EAAE,CAAC;IACZ,sCAAsC;IACtC,OAAO,CAAC,IAAI,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC;IAEzD,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC;IACvC,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,wCAAwC;IACxC,YAAY,CAAC,MAAM,GAAG,SAAS,aAAa,CAAI,OAAU;QACxD,MAAM,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC;QACxB,GAAG,CAAC,GAAG,GAAG,CAAC,IAAO,EAAE,EAAE;YACpB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;gBAC3B,sCAAsC;gBACtC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YACpE,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC;QACF,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC;IACvC,wCAAwC;IACxC,YAAY,CAAC,MAAM,GAAG,SAAS,aAAa,CAAC,EAAiB;QAC5D,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,OAAO,UAAU,CAAC,GAAG,EAAE;YACrB,MAAM,GAAG,GAAG,YAAY,EAAE,EAAE,GAAG,CAAC;YAChC,sCAAsC;YACtC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC;gBACH,OAAO,EAAE,EAAU,CAAC;YACtB,CAAC;oBAAS,CAAC;gBACT,sCAAsC;gBACtC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dev diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Warnings are intentionally best-effort: they never throw and they are printed
|
|
5
|
+
* once per code so noisy app renders do not flood the console.
|
|
6
|
+
*/
|
|
7
|
+
export declare function warnOnce(code: string, message: string): void;
|
|
8
|
+
export declare const _testHooks: {
|
|
9
|
+
resetWarnings(): void;
|
|
10
|
+
seenWarnings(): string[];
|
|
11
|
+
};
|