@origostudio/origo-design 0.0.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/.editorconfig +13 -0
- package/.gitattributes +22 -0
- package/.perf-baseline.json +12 -0
- package/.prettierignore +13 -0
- package/.prettierrc +10 -0
- package/.vscode/extensions.json +3 -0
- package/BRANCH-PROTECTION.md +25 -0
- package/CHANGELOG.md +292 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/commitlint.config.js +6 -0
- package/dist/packages/cli/CHANGELOG.md +92 -0
- package/dist/packages/cli/README.md +11 -0
- package/dist/packages/cli/package.json +16 -0
- package/dist/packages/cli/package.json.bak +16 -0
- package/dist/packages/cli/src/commands/generate/entity.d.ts +2 -0
- package/dist/packages/cli/src/commands/generate/entity.js +24 -0
- package/dist/packages/cli/src/commands/generate/entity.js.map +1 -0
- package/dist/packages/cli/src/commands/generate/index.d.ts +2 -0
- package/dist/packages/cli/src/commands/generate/index.js +30 -0
- package/dist/packages/cli/src/commands/generate/index.js.map +1 -0
- package/dist/packages/cli/src/commands/init.d.ts +5 -0
- package/dist/packages/cli/src/commands/init.js +27 -0
- package/dist/packages/cli/src/commands/init.js.map +1 -0
- package/dist/packages/cli/src/commands/new.d.ts +2 -0
- package/dist/packages/cli/src/commands/new.js +24 -0
- package/dist/packages/cli/src/commands/new.js.map +1 -0
- package/dist/packages/cli/src/commands/validate.d.ts +2 -0
- package/dist/packages/cli/src/commands/validate.js +50 -0
- package/dist/packages/cli/src/commands/validate.js.map +1 -0
- package/dist/packages/cli/src/index.d.ts +11 -0
- package/dist/packages/cli/src/index.js +15 -0
- package/dist/packages/cli/src/index.js.map +1 -0
- package/dist/packages/cli/src/lib/cli.d.ts +1 -0
- package/dist/packages/cli/src/lib/cli.js +7 -0
- package/dist/packages/cli/src/lib/cli.js.map +1 -0
- package/dist/packages/cli/src/lib/generator.d.ts +16 -0
- package/dist/packages/cli/src/lib/generator.js +165 -0
- package/dist/packages/cli/src/lib/generator.js.map +1 -0
- package/dist/packages/cli/src/lib/scaffolding.d.ts +5 -0
- package/dist/packages/cli/src/lib/scaffolding.js +87 -0
- package/dist/packages/cli/src/lib/scaffolding.js.map +1 -0
- package/dist/packages/cli/src/lib/validation.d.ts +4 -0
- package/dist/packages/cli/src/lib/validation.js +191 -0
- package/dist/packages/cli/src/lib/validation.js.map +1 -0
- package/dist/packages/cli/src/main.d.ts +4 -0
- package/dist/packages/cli/src/main.js +38 -0
- package/dist/packages/cli/src/main.js.map +1 -0
- package/dist/packages/cli/src/templates/entity.json +6 -0
- package/dist/packages/cli/src/templates/extension.json +8 -0
- package/dist/packages/cli/src/templates/index.d.ts +29 -0
- package/dist/packages/cli/src/templates/index.js +61 -0
- package/dist/packages/cli/src/templates/index.js.map +1 -0
- package/dist/packages/cli/src/templates/origo.json +16 -0
- package/dist/packages/cli/src/utils/errors.d.ts +15 -0
- package/dist/packages/cli/src/utils/errors.js +58 -0
- package/dist/packages/cli/src/utils/errors.js.map +1 -0
- package/dist/packages/core/CHANGELOG.md +275 -0
- package/dist/packages/core/README.md +11 -0
- package/dist/packages/core/package.json +21 -0
- package/dist/packages/core/package.json.bak +21 -0
- package/dist/packages/core/src/extension-api/extension-manager.d.ts +35 -0
- package/dist/packages/core/src/extension-api/extension-manager.js +300 -0
- package/dist/packages/core/src/extension-api/extension-manager.js.map +1 -0
- package/dist/packages/core/src/extension-api/index.d.ts +2 -0
- package/dist/packages/core/src/extension-api/index.js +6 -0
- package/dist/packages/core/src/extension-api/index.js.map +1 -0
- package/dist/packages/core/src/extension-api/types.d.ts +34 -0
- package/dist/packages/core/src/extension-api/types.js +3 -0
- package/dist/packages/core/src/extension-api/types.js.map +1 -0
- package/dist/packages/core/src/index.d.ts +3 -0
- package/dist/packages/core/src/index.js +7 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/schemas/capability.schema.json +104 -0
- package/dist/packages/core/src/schemas/contract.schema.json +52 -0
- package/dist/packages/core/src/schemas/domain.schema.json +47 -0
- package/dist/packages/core/src/schemas/entity.schema.json +72 -0
- package/dist/packages/core/src/schemas/extension.schema.json +39 -0
- package/dist/packages/core/src/schemas/permission.schema.json +21 -0
- package/dist/packages/core/src/types/ast.d.ts +17 -0
- package/dist/packages/core/src/types/ast.js +3 -0
- package/dist/packages/core/src/types/ast.js.map +1 -0
- package/dist/packages/core/src/types/domain.d.ts +101 -0
- package/dist/packages/core/src/types/domain.js +8 -0
- package/dist/packages/core/src/types/domain.js.map +1 -0
- package/dist/packages/core/src/types/validation.d.ts +12 -0
- package/dist/packages/core/src/types/validation.js +16 -0
- package/dist/packages/core/src/types/validation.js.map +1 -0
- package/dist/packages/core/src/validator/ast-validator.d.ts +11 -0
- package/dist/packages/core/src/validator/ast-validator.js +388 -0
- package/dist/packages/core/src/validator/ast-validator.js.map +1 -0
- package/dist/packages/core/src/validator/index.d.ts +23 -0
- package/dist/packages/core/src/validator/index.js +143 -0
- package/dist/packages/core/src/validator/index.js.map +1 -0
- package/dist/packages/core/src/validator/serializer.d.ts +11 -0
- package/dist/packages/core/src/validator/serializer.js +99 -0
- package/dist/packages/core/src/validator/serializer.js.map +1 -0
- package/eslint.config.js +114 -0
- package/generate_prompts.js +19 -0
- package/jest.config.ts +5 -0
- package/jest.preset.js +3 -0
- package/nx.json +169 -0
- package/package.json +115 -0
- package/packages/angular-renderer/CHANGELOG.md +274 -0
- package/packages/angular-renderer/README.md +11 -0
- package/packages/angular-renderer/package.json +20 -0
- package/packages/angular-renderer/playwright-ct.config.ts +23 -0
- package/packages/angular-renderer/src/adapters/web/adapter.ts +145 -0
- package/packages/angular-renderer/src/adapters/web/experience-adapter.service.ts +21 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.html +9 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.pw.ts +19 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.scss +31 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.ts +67 -0
- package/packages/angular-renderer/src/components/primitives/primitives.a11y.pw.ts +33 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.html +10 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.pw.ts +29 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.scss +42 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.ts +84 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.html +2 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.pw.ts +19 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.scss +5 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.ts +74 -0
- package/packages/angular-renderer/src/index.ts +7 -0
- package/packages/angular-renderer/src/lib/renderer.component.ts +118 -0
- package/packages/angular-renderer/src/lib/renderer.tokens.ts +9 -0
- package/packages/angular-renderer/src/lib/test-utils.ts +26 -0
- package/packages/angular-renderer/src/lib/theme.provider.ts +83 -0
- package/packages/angular-renderer/src/test-setup.ts +8 -0
- package/packages/cli/CHANGELOG.md +92 -0
- package/packages/cli/README.md +11 -0
- package/packages/cli/package.json +16 -0
- package/packages/cli/src/commands/generate/entity.ts +20 -0
- package/packages/cli/src/commands/generate/index.ts +28 -0
- package/packages/cli/src/commands/init.ts +28 -0
- package/packages/cli/src/commands/new.ts +22 -0
- package/packages/cli/src/commands/validate.ts +47 -0
- package/packages/cli/src/index.ts +11 -0
- package/packages/cli/src/lib/cli.ts +3 -0
- package/packages/cli/src/lib/generator.ts +181 -0
- package/packages/cli/src/lib/scaffolding.ts +104 -0
- package/packages/cli/src/lib/validation.ts +218 -0
- package/packages/cli/src/main.ts +38 -0
- package/packages/cli/src/templates/entity.json +6 -0
- package/packages/cli/src/templates/extension.json +8 -0
- package/packages/cli/src/templates/index.ts +94 -0
- package/packages/cli/src/templates/origo.json +16 -0
- package/packages/cli/src/utils/errors.ts +80 -0
- package/packages/core/CHANGELOG.md +275 -0
- package/packages/core/README.md +11 -0
- package/packages/core/package-lock.json +1035 -0
- package/packages/core/package.json +21 -0
- package/packages/core/scripts/generate-types.ts +23 -0
- package/packages/core/src/extension-api/extension-manager.ts +378 -0
- package/packages/core/src/extension-api/index.ts +2 -0
- package/packages/core/src/extension-api/types.ts +37 -0
- package/packages/core/src/index.ts +3 -0
- package/packages/core/src/schemas/__fixtures__/target-page.json +222 -0
- package/packages/core/src/schemas/capability.schema.json +104 -0
- package/packages/core/src/schemas/contract.schema.json +52 -0
- package/packages/core/src/schemas/domain.schema.json +47 -0
- package/packages/core/src/schemas/entity.schema.json +72 -0
- package/packages/core/src/schemas/extension.schema.json +39 -0
- package/packages/core/src/schemas/permission.schema.json +21 -0
- package/packages/core/src/types/ast.ts +20 -0
- package/packages/core/src/types/domain.ts +105 -0
- package/packages/core/src/types/validation.ts +29 -0
- package/packages/core/src/validator/ast-validator.ts +406 -0
- package/packages/core/src/validator/index.ts +207 -0
- package/packages/core/src/validator/serializer.ts +120 -0
- package/packages/core/test/fixtures/target-page.json +87 -0
- package/packages/design-tokens/CHANGELOG.md +273 -0
- package/packages/design-tokens/README.md +11 -0
- package/packages/design-tokens/package.json +25 -0
- package/packages/design-tokens/src/build.ts +97 -0
- package/packages/design-tokens/src/compile.ts +60 -0
- package/packages/design-tokens/src/index.ts +7 -0
- package/packages/design-tokens/src/runtime/index.ts +2 -0
- package/packages/design-tokens/src/runtime/theme-fetcher.ts +90 -0
- package/packages/design-tokens/src/runtime/theme-injector.ts +149 -0
- package/packages/design-tokens/src/schemas/base-tokens.schema.json +127 -0
- package/packages/design-tokens/src/schemas/semantic-tokens.schema.json +52 -0
- package/packages/design-tokens/src/tokens/base.json +46 -0
- package/packages/design-tokens/src/tokens/semantic.json +33 -0
- package/packages/playground/CHANGELOG.md +12 -0
- package/packages/playground/package.json +10 -0
- package/packages/playground/public/favicon.ico +0 -0
- package/packages/playground/src/app/app.component.html +1 -0
- package/packages/playground/src/app/app.component.scss +4 -0
- package/packages/playground/src/app/app.component.ts +13 -0
- package/packages/playground/src/app/app.config.ts +9 -0
- package/packages/playground/src/editor/badl-editor.component.html +1 -0
- package/packages/playground/src/editor/badl-editor.component.scss +10 -0
- package/packages/playground/src/editor/badl-editor.component.ts +57 -0
- package/packages/playground/src/editor/editor.worker.ts +1 -0
- package/packages/playground/src/editor/json.worker.ts +1 -0
- package/packages/playground/src/editor/monaco-environment.ts +11 -0
- package/packages/playground/src/editor/schema-registry.ts +30 -0
- package/packages/playground/src/index.html +13 -0
- package/packages/playground/src/main.ts +5 -0
- package/packages/playground/src/schemas/capability.schema.json +104 -0
- package/packages/playground/src/schemas/contract.schema.json +52 -0
- package/packages/playground/src/schemas/domain.schema.json +47 -0
- package/packages/playground/src/schemas/entity.schema.json +72 -0
- package/packages/playground/src/schemas/extension.schema.json +39 -0
- package/packages/playground/src/schemas/permission.schema.json +21 -0
- package/packages/playground/src/styles.scss +8 -0
- package/packages/playground/src/test-setup.ts +146 -0
- package/packages/playground/vite.config.ts +12 -0
- package/packages/playground/vitest.config.mjs +24 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import baseTokensSchema from './schemas/base-tokens.schema.json';
|
|
2
|
+
import semanticTokensSchema from './schemas/semantic-tokens.schema.json';
|
|
3
|
+
import baseTokens from './tokens/base.json';
|
|
4
|
+
import semanticTokens from './tokens/semantic.json';
|
|
5
|
+
|
|
6
|
+
export * from './build';
|
|
7
|
+
export { baseTokensSchema, semanticTokensSchema, baseTokens, semanticTokens };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { injectTheme } from './theme-injector';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetches a theme.json file from the specified URL.
|
|
5
|
+
*
|
|
6
|
+
* - Gracefully handles SSR environments by returning an empty object.
|
|
7
|
+
* - Handles network errors by catching them and returning an empty object.
|
|
8
|
+
*
|
|
9
|
+
* @param url The URL to fetch the theme JSON from.
|
|
10
|
+
* @returns A promise that resolves to the parsed theme JSON object, or an empty object on failure/SSR.
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchTheme(url: string): Promise<Record<string, unknown>> {
|
|
13
|
+
// SSR guard: fetch might not exist, or we are in a server environment
|
|
14
|
+
if (typeof fetch === 'undefined' || typeof document === 'undefined') {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetch(url);
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
console.warn(
|
|
22
|
+
`[origo-design] Failed to fetch theme from ${url}: ${response.status} ${response.statusText}`
|
|
23
|
+
);
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
return data;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.warn(`[origo-design] Error fetching theme from ${url}:`, error);
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const activeLoads = new Map<unknown, number>();
|
|
35
|
+
let globalLoadCounter = 0;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fetches a theme.json file and injects it into the DOM.
|
|
39
|
+
* Measures the time taken using the Performance API to ensure NFR-PERF-005 limits.
|
|
40
|
+
*
|
|
41
|
+
* @param url The URL to fetch the theme JSON from.
|
|
42
|
+
* @param target The target HTMLElement to scope the injected styles to. Defaults to documentElement.
|
|
43
|
+
* @returns A promise that resolves to a teardown function to remove the injected style tag.
|
|
44
|
+
*/
|
|
45
|
+
export async function loadAndInjectTheme(
|
|
46
|
+
url: string,
|
|
47
|
+
target?: HTMLElement | null
|
|
48
|
+
): Promise<() => void> {
|
|
49
|
+
const loadId = ++globalLoadCounter;
|
|
50
|
+
const loadKey = target || 'default';
|
|
51
|
+
activeLoads.set(loadKey, loadId);
|
|
52
|
+
const perfMarkStart = `theme-load-start-${url}`;
|
|
53
|
+
const perfMarkEnd = `theme-load-end-${url}`;
|
|
54
|
+
const perfMeasure = `theme-load-measure-${url}`;
|
|
55
|
+
|
|
56
|
+
if (typeof performance !== 'undefined' && performance.mark) {
|
|
57
|
+
performance.mark(perfMarkStart);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let teardown: () => void = () => {
|
|
61
|
+
// default no-op teardown
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
const themeJson = await fetchTheme(url);
|
|
65
|
+
// Concurrency Lock: abort injection if a newer call has superseded this one for the same target
|
|
66
|
+
if (activeLoads.get(loadKey) !== loadId) {
|
|
67
|
+
return teardown;
|
|
68
|
+
}
|
|
69
|
+
const injected = injectTheme(themeJson, target);
|
|
70
|
+
if (typeof injected === 'function') {
|
|
71
|
+
teardown = injected;
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.warn('[origo-design] Error in loadAndInjectTheme', error);
|
|
75
|
+
} finally {
|
|
76
|
+
if (typeof performance !== 'undefined' && performance.mark && performance.measure) {
|
|
77
|
+
performance.mark(perfMarkEnd);
|
|
78
|
+
try {
|
|
79
|
+
performance.measure(perfMeasure, perfMarkStart, perfMarkEnd);
|
|
80
|
+
} catch {
|
|
81
|
+
// Ignore measure errors
|
|
82
|
+
}
|
|
83
|
+
performance.clearMarks(perfMarkStart);
|
|
84
|
+
performance.clearMarks(perfMarkEnd);
|
|
85
|
+
performance.clearMeasures(perfMeasure);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return teardown;
|
|
90
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** CSS custom property name allowlist — alphanumeric, hyphens, underscores only */
|
|
2
|
+
const CSS_SAFE_KEY = /^[a-zA-Z0-9_-]+$/;
|
|
3
|
+
|
|
4
|
+
/** Keys that must never be set on a plain object to prevent prototype pollution */
|
|
5
|
+
const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Allowed CSS value patterns for semantic token overrides.
|
|
9
|
+
* Validates concrete formats rather than relying solely on a denylist.
|
|
10
|
+
*/
|
|
11
|
+
const SAFE_VALUE_PATTERNS: RegExp[] = [
|
|
12
|
+
/^#[0-9a-fA-F]{3,8}$/, // hex colors: #fff, #ffffff, #ffffffaa
|
|
13
|
+
/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+(?:\s*,\s*[\d.]+)?\s*\)$/, // rgb / rgba
|
|
14
|
+
/^hsla?\(\s*[\d.]+\s*,\s*[\d.]+%\s*,\s*[\d.]+%(?:\s*,\s*[\d.]+)?\s*\)$/, // hsl / hsla
|
|
15
|
+
/^[\d.]+(?:px|rem|em|%|vh|vw|vmin|vmax|pt|ch|ex|fr|deg|rad|turn|ms|s)$/, // numeric with units
|
|
16
|
+
/^[\d.]+$/, // plain numbers (opacity, z-index, line-height)
|
|
17
|
+
/^[a-zA-Z][-a-zA-Z0-9]*$/, // CSS named values: red, transparent, inherit, currentColor, etc.
|
|
18
|
+
/^var\(--[a-zA-Z0-9_-]+\)$/, // CSS variables
|
|
19
|
+
/^calc\([^)]+\)$/, // CSS calc
|
|
20
|
+
/^clamp\([^)]+\)$/, // CSS clamp
|
|
21
|
+
/^(?:linear|radial|conic)-gradient\([^)]+\)$/, // CSS gradients
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function isSafeValue(value: string): boolean {
|
|
25
|
+
return SAFE_VALUE_PATTERNS.some(pattern => pattern.test(value.trim()));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parses and sanitises a flat or single-level-nested theme JSON object into
|
|
30
|
+
* a safe map of `token-key → CSS value`.
|
|
31
|
+
*
|
|
32
|
+
* - Keys are validated against a CSS identifier allowlist.
|
|
33
|
+
* - Values are validated against explicit CSS format patterns.
|
|
34
|
+
* - Prototype-poisoning keys (`__proto__`, `constructor`, `prototype`) are blocked.
|
|
35
|
+
* - Arrays are rejected at every nesting level.
|
|
36
|
+
*/
|
|
37
|
+
export function parseTheme(themeJson: unknown): Record<string, string> {
|
|
38
|
+
if (!themeJson || typeof themeJson !== 'object' || Array.isArray(themeJson)) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Object.create(null) avoids prototype setter side-effects for any key written
|
|
43
|
+
const result = Object.create(null) as Record<string, string>;
|
|
44
|
+
|
|
45
|
+
const flatten = (
|
|
46
|
+
obj: Record<string, unknown>,
|
|
47
|
+
prefix = '',
|
|
48
|
+
depth = 0,
|
|
49
|
+
visited = new WeakSet()
|
|
50
|
+
) => {
|
|
51
|
+
if (depth > 1) return; // Prevent arbitrary depth beyond single-level
|
|
52
|
+
if (visited.has(obj)) return; // Prevent circular reference crashes
|
|
53
|
+
visited.add(obj);
|
|
54
|
+
|
|
55
|
+
for (const key of Object.keys(obj)) {
|
|
56
|
+
if (BLOCKED_KEYS.has(key) || !CSS_SAFE_KEY.test(key)) continue;
|
|
57
|
+
|
|
58
|
+
const value = obj[key];
|
|
59
|
+
|
|
60
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
61
|
+
flatten(value as Record<string, unknown>, `${prefix}${key}-`, depth + 1, visited);
|
|
62
|
+
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
63
|
+
const strVal = String(value).trim();
|
|
64
|
+
const fullKey = `${prefix}${key}`;
|
|
65
|
+
|
|
66
|
+
// Block base token overrides, enforce semantic overrides
|
|
67
|
+
if (!fullKey.startsWith('base-') && isSafeValue(strVal)) {
|
|
68
|
+
result[fullKey] = strVal;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
flatten(themeJson as Record<string, unknown>);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Applies a parsed theme to the DOM by injecting a `<style>` tag into
|
|
80
|
+
* `document.head` scoped to the target element.
|
|
81
|
+
*
|
|
82
|
+
* - When `target` is `document.documentElement` (the default), the style block
|
|
83
|
+
* targets `:root` for global override.
|
|
84
|
+
* - When `target` is any other element, the element receives a stable
|
|
85
|
+
* `data-origo-theme-id` attribute and the style block is scoped to that
|
|
86
|
+
* attribute selector, enabling per-subtree theme isolation.
|
|
87
|
+
* - Passing an empty or fully-invalid theme removes the previously injected
|
|
88
|
+
* style tag for that target rather than leaving stale rules.
|
|
89
|
+
* - Safe to call in SSR environments — returns immediately if `document` is
|
|
90
|
+
* not defined.
|
|
91
|
+
*/
|
|
92
|
+
let nextTagId = 0;
|
|
93
|
+
|
|
94
|
+
export function injectTheme(themeJson: unknown, target?: HTMLElement | null): () => void {
|
|
95
|
+
// SSR guard — DOM APIs are not available in Node.js environments
|
|
96
|
+
if (typeof document === 'undefined') return () => undefined;
|
|
97
|
+
|
|
98
|
+
const actualTarget = target === undefined ? document.documentElement : target;
|
|
99
|
+
if (!actualTarget) return () => undefined;
|
|
100
|
+
|
|
101
|
+
const head = document.head || document.documentElement;
|
|
102
|
+
if (!head) return () => undefined;
|
|
103
|
+
|
|
104
|
+
const parsed = parseTheme(themeJson);
|
|
105
|
+
|
|
106
|
+
// Determine the CSS selector and the style-tag attribute for this target
|
|
107
|
+
let selector: string;
|
|
108
|
+
let tagId: string;
|
|
109
|
+
|
|
110
|
+
if (actualTarget === document.documentElement) {
|
|
111
|
+
selector = ':root';
|
|
112
|
+
tagId = 'runtime';
|
|
113
|
+
} else {
|
|
114
|
+
// Assign a stable per-element ID so repeated calls reuse the same tag
|
|
115
|
+
if (!actualTarget.dataset['origoThemeId']) {
|
|
116
|
+
actualTarget.dataset['origoThemeId'] = `t${++nextTagId}`;
|
|
117
|
+
}
|
|
118
|
+
tagId = actualTarget.dataset['origoThemeId'] as string;
|
|
119
|
+
selector = `[data-origo-theme-id="${CSS.escape(tagId)}"]`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let styleTag = head.querySelector<HTMLStyleElement>(
|
|
123
|
+
`style[data-origo-theme="${CSS.escape(tagId)}"]`
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Empty theme → remove the style tag to prevent stale overrides
|
|
127
|
+
if (Object.keys(parsed).length === 0) {
|
|
128
|
+
styleTag?.remove();
|
|
129
|
+
return () => undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!styleTag) {
|
|
133
|
+
styleTag = document.createElement('style');
|
|
134
|
+
styleTag.setAttribute('data-origo-theme', tagId);
|
|
135
|
+
head.appendChild(styleTag);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let cssVariables = '';
|
|
139
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
140
|
+
cssVariables += ` --origo-${key}: ${value};\n`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
styleTag.textContent = `${selector} {\n${cssVariables}}\n`;
|
|
144
|
+
|
|
145
|
+
// Return a teardown function to clean up scoped injections
|
|
146
|
+
return () => {
|
|
147
|
+
styleTag?.remove();
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://origo.dev/schemas/base-tokens.schema.json",
|
|
4
|
+
"title": "Origo Base Tokens Schema",
|
|
5
|
+
"description": "JSON Schema for Origo Base Tokens",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"patternProperties": {
|
|
8
|
+
"^[^$][a-zA-Z0-9_\\-]*$": {
|
|
9
|
+
"$ref": "#/$defs/tokenOrGroup"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"$defs": {
|
|
14
|
+
"tokenOrGroup": {
|
|
15
|
+
"anyOf": [{ "$ref": "#/$defs/token" }, { "$ref": "#/$defs/tokenGroup" }]
|
|
16
|
+
},
|
|
17
|
+
"token": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"$value": {
|
|
21
|
+
"type": ["string", "number", "object", "array", "boolean"]
|
|
22
|
+
},
|
|
23
|
+
"$type": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"enum": [
|
|
26
|
+
"color",
|
|
27
|
+
"dimension",
|
|
28
|
+
"fontFamily",
|
|
29
|
+
"fontWeight",
|
|
30
|
+
"duration",
|
|
31
|
+
"cubicBezier",
|
|
32
|
+
"number",
|
|
33
|
+
"shadow",
|
|
34
|
+
"strokeStyle",
|
|
35
|
+
"border",
|
|
36
|
+
"transition",
|
|
37
|
+
"typography",
|
|
38
|
+
"spacing",
|
|
39
|
+
"borderRadius",
|
|
40
|
+
"elevation",
|
|
41
|
+
"animation",
|
|
42
|
+
"motion",
|
|
43
|
+
"breakpoints",
|
|
44
|
+
"opacity",
|
|
45
|
+
"density"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"$description": {
|
|
49
|
+
"type": "string"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": ["$value"],
|
|
53
|
+
"additionalProperties": false,
|
|
54
|
+
"allOf": [
|
|
55
|
+
{
|
|
56
|
+
"if": { "properties": { "$type": { "const": "typography" } }, "required": ["$type"] },
|
|
57
|
+
"then": { "properties": { "$value": { "$ref": "#/$defs/typographyValue" } } }
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"if": { "properties": { "$type": { "const": "shadow" } }, "required": ["$type"] },
|
|
61
|
+
"then": { "properties": { "$value": { "$ref": "#/$defs/shadowValue" } } }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"if": { "properties": { "$type": { "const": "border" } }, "required": ["$type"] },
|
|
65
|
+
"then": { "properties": { "$value": { "$ref": "#/$defs/borderValue" } } }
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"tokenGroup": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": {
|
|
72
|
+
"$type": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
"$description": {
|
|
76
|
+
"type": "string"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"patternProperties": {
|
|
80
|
+
"^[^$][a-zA-Z0-9_\\-]*$": {
|
|
81
|
+
"$ref": "#/$defs/tokenOrGroup"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"additionalProperties": false
|
|
85
|
+
},
|
|
86
|
+
"typographyValue": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"fontFamily": { "type": "string" },
|
|
90
|
+
"fontSize": { "type": ["string", "number"] },
|
|
91
|
+
"fontWeight": { "type": ["string", "number"] },
|
|
92
|
+
"letterSpacing": { "type": ["string", "number"] },
|
|
93
|
+
"lineHeight": { "type": ["string", "number"] }
|
|
94
|
+
},
|
|
95
|
+
"required": ["fontFamily", "fontSize"],
|
|
96
|
+
"additionalProperties": false
|
|
97
|
+
},
|
|
98
|
+
"shadowValue": {
|
|
99
|
+
"anyOf": [
|
|
100
|
+
{ "$ref": "#/$defs/singleShadowValue" },
|
|
101
|
+
{ "type": "array", "items": { "$ref": "#/$defs/singleShadowValue" } }
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"singleShadowValue": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"color": { "type": "string" },
|
|
108
|
+
"offsetX": { "type": ["string", "number"] },
|
|
109
|
+
"offsetY": { "type": ["string", "number"] },
|
|
110
|
+
"blur": { "type": ["string", "number"] },
|
|
111
|
+
"spread": { "type": ["string", "number"] }
|
|
112
|
+
},
|
|
113
|
+
"required": ["color", "offsetX", "offsetY"],
|
|
114
|
+
"additionalProperties": false
|
|
115
|
+
},
|
|
116
|
+
"borderValue": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"color": { "type": "string" },
|
|
120
|
+
"width": { "type": ["string", "number"] },
|
|
121
|
+
"style": { "type": "string" }
|
|
122
|
+
},
|
|
123
|
+
"required": ["color", "width", "style"],
|
|
124
|
+
"additionalProperties": false
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://origo.dev/schemas/semantic-tokens.schema.json",
|
|
4
|
+
"title": "Origo Semantic Tokens Schema",
|
|
5
|
+
"description": "JSON Schema for Origo Semantic Tokens (ensures references)",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"patternProperties": {
|
|
8
|
+
"^[^$][a-zA-Z0-9_\\-]*$": {
|
|
9
|
+
"$ref": "#/$defs/tokenOrGroup"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"$defs": {
|
|
14
|
+
"tokenOrGroup": {
|
|
15
|
+
"anyOf": [{ "$ref": "#/$defs/token" }, { "$ref": "#/$defs/tokenGroup" }]
|
|
16
|
+
},
|
|
17
|
+
"token": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"$value": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^.*\\{[a-zA-Z0-9_\\-\\.]+\\}.*$"
|
|
23
|
+
},
|
|
24
|
+
"$type": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"$description": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": ["$value"],
|
|
32
|
+
"additionalProperties": false
|
|
33
|
+
},
|
|
34
|
+
"tokenGroup": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"$type": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"$description": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"patternProperties": {
|
|
45
|
+
"^[^$][a-zA-Z0-9_\\-]*$": {
|
|
46
|
+
"$ref": "#/$defs/tokenOrGroup"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"color": {
|
|
3
|
+
"base": {
|
|
4
|
+
"blue": {
|
|
5
|
+
"100": {
|
|
6
|
+
"$value": "#E6F0FF",
|
|
7
|
+
"$type": "color"
|
|
8
|
+
},
|
|
9
|
+
"500": {
|
|
10
|
+
"$value": "#0066FF",
|
|
11
|
+
"$type": "color"
|
|
12
|
+
},
|
|
13
|
+
"900": {
|
|
14
|
+
"$value": "#002966",
|
|
15
|
+
"$type": "color"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"neutral": {
|
|
19
|
+
"100": {
|
|
20
|
+
"$value": "#F5F5F5",
|
|
21
|
+
"$type": "color"
|
|
22
|
+
},
|
|
23
|
+
"900": {
|
|
24
|
+
"$value": "#1A1A1A",
|
|
25
|
+
"$type": "color"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"spacing": {
|
|
31
|
+
"base": {
|
|
32
|
+
"1": {
|
|
33
|
+
"$value": "4px",
|
|
34
|
+
"$type": "dimension"
|
|
35
|
+
},
|
|
36
|
+
"2": {
|
|
37
|
+
"$value": "8px",
|
|
38
|
+
"$type": "dimension"
|
|
39
|
+
},
|
|
40
|
+
"4": {
|
|
41
|
+
"$value": "16px",
|
|
42
|
+
"$type": "dimension"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"color": {
|
|
3
|
+
"surface": {
|
|
4
|
+
"primary": {
|
|
5
|
+
"$value": "{color.base.blue.500}",
|
|
6
|
+
"$type": "color",
|
|
7
|
+
"$description": "Primary surface color for main actions"
|
|
8
|
+
},
|
|
9
|
+
"background": {
|
|
10
|
+
"$value": "{color.base.neutral.100}",
|
|
11
|
+
"$type": "color"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"text": {
|
|
15
|
+
"primary": {
|
|
16
|
+
"$value": "{color.base.neutral.900}",
|
|
17
|
+
"$type": "color"
|
|
18
|
+
},
|
|
19
|
+
"inverse": {
|
|
20
|
+
"$value": "{color.base.neutral.100}",
|
|
21
|
+
"$type": "color"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"spacing": {
|
|
26
|
+
"container": {
|
|
27
|
+
"padding": {
|
|
28
|
+
"$value": "{spacing.base.4}",
|
|
29
|
+
"$type": "dimension"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## 0.0.22 (2026-09-04)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- add tsconfig.app.json to playground package for application-specific build configuration ([3a9d8e4](https://github.com/OrigoStudio/origo-design/commit/3a9d8e4))
|
|
6
|
+
- add test setup configuration for vitest and browser environment mocks ([e75ad98](https://github.com/OrigoStudio/origo-design/commit/e75ad98))
|
|
7
|
+
- scaffold playground application and e2e testing suite with initial editor component and schema registry ([f097894](https://github.com/OrigoStudio/origo-design/commit/f097894))
|
|
8
|
+
- add GitHub Actions workflow to automate library releases using Nx Release ([3d6651f](https://github.com/OrigoStudio/origo-design/commit/3d6651f))
|
|
9
|
+
|
|
10
|
+
### ❤️ Thank You
|
|
11
|
+
|
|
12
|
+
- Hardik Patel @hardikpatelse
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<origo-badl-editor [initialValue]="'{}'"></origo-badl-editor>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { BadlEditorComponent } from '../editor/badl-editor.component';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
standalone: true,
|
|
6
|
+
imports: [BadlEditorComponent],
|
|
7
|
+
selector: 'origo-root',
|
|
8
|
+
templateUrl: './app.component.html',
|
|
9
|
+
styleUrl: './app.component.scss',
|
|
10
|
+
})
|
|
11
|
+
export class AppComponent {
|
|
12
|
+
protected title = 'playground';
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApplicationConfig,
|
|
3
|
+
provideBrowserGlobalErrorListeners,
|
|
4
|
+
provideZonelessChangeDetection,
|
|
5
|
+
} from '@angular/core';
|
|
6
|
+
|
|
7
|
+
export const appConfig: ApplicationConfig = {
|
|
8
|
+
providers: [provideBrowserGlobalErrorListeners(), provideZonelessChangeDetection()],
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="editor-container" #editorContainer></div>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
AfterViewInit,
|
|
4
|
+
OnDestroy,
|
|
5
|
+
ElementRef,
|
|
6
|
+
ViewChild,
|
|
7
|
+
input,
|
|
8
|
+
NgZone,
|
|
9
|
+
inject,
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import * as monaco from 'monaco-editor';
|
|
12
|
+
import './monaco-environment'; // MUST be first monaco import — side-effect only
|
|
13
|
+
import { registerBadlSchema } from './schema-registry';
|
|
14
|
+
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'origo-badl-editor',
|
|
17
|
+
standalone: true,
|
|
18
|
+
templateUrl: './badl-editor.component.html',
|
|
19
|
+
styleUrls: ['./badl-editor.component.scss'],
|
|
20
|
+
})
|
|
21
|
+
export class BadlEditorComponent implements AfterViewInit, OnDestroy {
|
|
22
|
+
@ViewChild('editorContainer') editorContainer!: ElementRef<HTMLDivElement>;
|
|
23
|
+
|
|
24
|
+
readonly initialValue = input<string>('');
|
|
25
|
+
readonly theme = input<string>('vs-dark');
|
|
26
|
+
readonly readOnly = input<boolean>(false);
|
|
27
|
+
|
|
28
|
+
private editor: monaco.editor.IStandaloneCodeEditor | null = null;
|
|
29
|
+
private zone = inject(NgZone);
|
|
30
|
+
|
|
31
|
+
ngAfterViewInit(): void {
|
|
32
|
+
registerBadlSchema();
|
|
33
|
+
this.zone.runOutsideAngular(() => {
|
|
34
|
+
if (!this.editorContainer?.nativeElement) return;
|
|
35
|
+
try {
|
|
36
|
+
this.editor = monaco.editor.create(this.editorContainer.nativeElement, {
|
|
37
|
+
model: monaco.editor.createModel(
|
|
38
|
+
this.initialValue(),
|
|
39
|
+
'json',
|
|
40
|
+
monaco.Uri.parse('inmemory://model/domain.json')
|
|
41
|
+
),
|
|
42
|
+
theme: this.theme(),
|
|
43
|
+
readOnly: this.readOnly(),
|
|
44
|
+
automaticLayout: true, // Required for split-pane resize in Story 7.2
|
|
45
|
+
minimap: { enabled: false },
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error('Failed to initialize Monaco Editor', e);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ngOnDestroy(): void {
|
|
54
|
+
this.editor?.dispose(); // CRITICAL — prevents Monaco timer memory leak
|
|
55
|
+
this.editor = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'monaco-editor/esm/vs/editor/editor.worker';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'monaco-editor/esm/vs/language/json/json.worker';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// packages/playground/src/editor/monaco-environment.ts
|
|
2
|
+
// This file MUST be imported before ANY other monaco-editor import (side-effect)
|
|
3
|
+
|
|
4
|
+
(window as Window & typeof globalThis & { MonacoEnvironment: unknown }).MonacoEnvironment = {
|
|
5
|
+
getWorker(_: string, label: string) {
|
|
6
|
+
if (label === 'json') {
|
|
7
|
+
return new Worker(new URL('./json.worker', import.meta.url), { type: 'module' });
|
|
8
|
+
}
|
|
9
|
+
return new Worker(new URL('./editor.worker', import.meta.url), { type: 'module' });
|
|
10
|
+
},
|
|
11
|
+
};
|