juo 0.3.0-alpha.1 → 0.3.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/dev.js +1 -0
- package/dist/commands/blocks/dev.d.ts +2 -2
- package/dist/commands/create.d.ts +4 -4
- package/dist/commands/create.js +85 -39
- package/dist/commands/generate.d.ts +5 -5
- package/dist/commands/publish/index.d.ts +2 -2
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -1
- package/dist/{packageGenerator-D81lWXTA.js → packageGenerator-CE68rONC.js} +4 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/templates/.storybook/preview-head.html.liquid +3 -5
- package/templates/block/preact/index.ts.liquid +65 -10
- package/templates/block/preact/{{block.clsName}}.tsx.liquid +100 -130
- package/templates/block/react/index.ts.liquid +64 -8
- package/templates/block/react/{{block.clsName}}.tsx.liquid +104 -53
- package/templates/configFiles/vite-env.d.ts.liquid +11 -1
- package/templates/css/styles.css +31 -35
- package/templates/dev/index.html.liquid +10 -1
- package/templates/dev/vite/juo-editor-plugin.ts.liquid +22 -1
- package/templates/dev/vite/mock-login.ts.liquid +83 -0
- package/templates/dev/vite/mock-services.ts.liquid +159 -0
- package/templates/dev/vite/theme-shell.ts.liquid +129 -3
- package/templates/dev/vite/workflowOverlay.ts.liquid +280 -0
- package/templates/stories/{{block.clsName}}.stories.tsx.liquid +6 -3
- package/templates/tailwind/tailwind.config.cjs.liquid +1 -1
package/templates/css/styles.css
CHANGED
|
@@ -1,59 +1,55 @@
|
|
|
1
1
|
@import "@juo/customer-ui/design-tokens.css";
|
|
2
2
|
@import "@juo/customer-ui/theme-modes.css";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/*
|
|
5
|
+
* Styling for the starter block (non-Tailwind projects).
|
|
6
|
+
*
|
|
7
|
+
* Everything below comes from the Customer UI design tokens imported above:
|
|
8
|
+
* the `--space-*` spacing scale, the `--font-size-*` type scale, the
|
|
9
|
+
* `--radius-*` radius scale, and the `--tint-*` (neutral) / `--accent-*`
|
|
10
|
+
* (brand) color palettes. Prefer these tokens over hardcoded values so the
|
|
11
|
+
* block follows the merchant's theme automatically.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
.starter-block {
|
|
6
15
|
display: flex;
|
|
7
|
-
gap: 2rem;
|
|
8
|
-
justify-content: space-between;
|
|
9
16
|
align-items: center;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
background-color: #fafafa;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
gap: var(--space-32);
|
|
19
|
+
padding: var(--space-32);
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
.
|
|
22
|
+
.starter-block__info {
|
|
17
23
|
display: flex;
|
|
18
24
|
align-items: center;
|
|
19
|
-
gap:
|
|
25
|
+
gap: var(--space-12);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
.
|
|
28
|
+
.starter-block__icons {
|
|
23
29
|
display: flex;
|
|
24
30
|
align-items: center;
|
|
25
|
-
gap:
|
|
31
|
+
gap: var(--space-8);
|
|
32
|
+
/* Defaults to a neutral color; overridden inline by the color control. */
|
|
33
|
+
color: var(--tint-900);
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
.
|
|
29
|
-
width:
|
|
30
|
-
height:
|
|
31
|
-
fill: currentColor;
|
|
32
|
-
color: black;
|
|
36
|
+
.starter-block__icon {
|
|
37
|
+
width: var(--space-32);
|
|
38
|
+
height: var(--space-32);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
.
|
|
36
|
-
width: 2.5rem;
|
|
37
|
-
height: 2.5rem;
|
|
38
|
-
fill: currentColor;
|
|
39
|
-
color: black;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.text-container {
|
|
41
|
+
.starter-block__text {
|
|
43
42
|
display: flex;
|
|
44
43
|
flex-direction: column;
|
|
45
|
-
align-self: center;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
.
|
|
49
|
-
font-size:
|
|
50
|
-
|
|
51
|
-
color: #696969;
|
|
46
|
+
.starter-block__subtitle {
|
|
47
|
+
font-size: var(--font-size-12);
|
|
48
|
+
color: var(--tint-500);
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
.
|
|
55
|
-
font-size:
|
|
56
|
-
|
|
57
|
-
color:
|
|
51
|
+
.starter-block__title {
|
|
52
|
+
font-size: var(--font-size-16);
|
|
53
|
+
font-weight: 500;
|
|
54
|
+
color: var(--tint-900);
|
|
58
55
|
}
|
|
59
|
-
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Juo Blocks Dev</title>
|
|
7
|
+
<link rel="preconnect" href="https://rsms.me/" />
|
|
8
|
+
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
9
|
+
<style>
|
|
10
|
+
body {
|
|
11
|
+
font-family: InterVariable, Inter, sans-serif;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
7
16
|
<script type="importmap">
|
|
8
17
|
{
|
|
9
18
|
"imports": {
|
|
@@ -14,7 +23,7 @@
|
|
|
14
23
|
</head>
|
|
15
24
|
<body>
|
|
16
25
|
<juo-context-root>
|
|
17
|
-
<div id="app"></div>
|
|
26
|
+
<div id="app" style="padding: 20px; max-width: 1080px; margin: 0 auto;"></div>
|
|
18
27
|
</juo-context-root>
|
|
19
28
|
<script type="module" src="/vite/theme-shell.ts"></script>
|
|
20
29
|
</body>
|
|
@@ -3,9 +3,28 @@ import type { Plugin } from "vite";
|
|
|
3
3
|
const EDITOR_EMBED_URL =
|
|
4
4
|
"https://cdn.juo.io/web/blocks/juo/editor/{{ editorVersion }}/juo-editor.embed.js";
|
|
5
5
|
|
|
6
|
+
const DEVTOOLS_EMBED_URL =
|
|
7
|
+
"https://cdn.juo.io/web/blocks/juo/devtools/{{ devtoolsVersion }}/blocks-devtools.embed.js";
|
|
8
|
+
|
|
6
9
|
export default function juo(): Plugin {
|
|
7
10
|
return {
|
|
8
11
|
name: "juo-editor",
|
|
12
|
+
|
|
13
|
+
transformIndexHtml(_html, ctx) {
|
|
14
|
+
// Skip the editor page itself — devtools aren't needed there.
|
|
15
|
+
if (ctx.path === "/editor" || ctx.path.startsWith("/editor?")) return;
|
|
16
|
+
return {
|
|
17
|
+
html: _html,
|
|
18
|
+
tags: [
|
|
19
|
+
{
|
|
20
|
+
tag: "script",
|
|
21
|
+
attrs: { type: "module", src: DEVTOOLS_EMBED_URL },
|
|
22
|
+
injectTo: "head-prepend",
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
|
|
9
28
|
configureServer(server) {
|
|
10
29
|
server.middlewares.use("/editor", (_req, res) => {
|
|
11
30
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
@@ -15,8 +34,10 @@ export default function juo(): Plugin {
|
|
|
15
34
|
<meta charset="UTF-8" />
|
|
16
35
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
17
36
|
<title>Juo Blocks Editor</title>
|
|
37
|
+
<link rel="preconnect" href="https://rsms.me/" />
|
|
38
|
+
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
18
39
|
<style>
|
|
19
|
-
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; }
|
|
40
|
+
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; font-family: InterVariable, Inter, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
|
20
41
|
juo-editor { display: block; height: 100vh; }
|
|
21
42
|
</style>
|
|
22
43
|
</head>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Dev-only mock login service. Ported from the reference theme's
|
|
2
|
+
// `lib/loginService.ts`. Core's `createLoginService` requires real deps
|
|
3
|
+
// (API adapter, router, shop, error formatter), so the dev preview hand-writes
|
|
4
|
+
// a mock that implements the `LoginServiceContext` shape directly.
|
|
5
|
+
import { ContextType, LoginServiceContext, signal } from "@juo/blocks";
|
|
6
|
+
|
|
7
|
+
export function createMockLoginService(): ContextType<typeof LoginServiceContext> {
|
|
8
|
+
const loggedIn = signal<boolean>(false);
|
|
9
|
+
const expectedCode = signal<string | null>(null);
|
|
10
|
+
|
|
11
|
+
const service: ContextType<typeof LoginServiceContext> = {
|
|
12
|
+
passwordless: {
|
|
13
|
+
email: {
|
|
14
|
+
async start(_) {
|
|
15
|
+
expectedCode.value = String(
|
|
16
|
+
Math.floor(100000 + Math.random() * 900000)
|
|
17
|
+
);
|
|
18
|
+
window.alert(`Use code ${expectedCode.value} to log in`);
|
|
19
|
+
|
|
20
|
+
return { _tag: "Success", data: undefined };
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async verify(_, code) {
|
|
24
|
+
if (expectedCode.value != null && code === expectedCode.value) {
|
|
25
|
+
loggedIn.value = true;
|
|
26
|
+
return { _tag: "Success", data: undefined };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
_tag: "Failure",
|
|
31
|
+
error: {
|
|
32
|
+
title: "Invalid code",
|
|
33
|
+
message: "The provided code is invalid",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
social: {
|
|
41
|
+
async start() {
|
|
42
|
+
return {
|
|
43
|
+
_tag: "Failure",
|
|
44
|
+
error: {
|
|
45
|
+
title: "Not implemented",
|
|
46
|
+
message: "This functionality is not available in test mode",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async verify() {
|
|
52
|
+
return {
|
|
53
|
+
_tag: "Failure",
|
|
54
|
+
error: {
|
|
55
|
+
title: "Not implemented",
|
|
56
|
+
message: "This functionality is not available in test mode",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async handleQueryParams() {
|
|
63
|
+
return { _tag: "Success", data: undefined };
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async logout() {
|
|
67
|
+
loggedIn.value = false;
|
|
68
|
+
return { _tag: "Success", data: undefined };
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
isAuthenticated: loggedIn,
|
|
72
|
+
|
|
73
|
+
async getToken() {
|
|
74
|
+
return null;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async getAuthHeaders(): Promise<Record<string, string>> {
|
|
78
|
+
return {};
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return service;
|
|
83
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Dev-only mock services. Provides every blocks service backed by its mock
|
|
2
|
+
// adapter so any block you author (or scaffold) has its service dependencies
|
|
3
|
+
// available in the dev preview. Lives in `vite/` and never enters the built
|
|
4
|
+
// blocks bundle (whose entry is `src/blocks/register.ts`), so there is no
|
|
5
|
+
// production cost — the Juo runtime provides the real services in production.
|
|
6
|
+
import {
|
|
7
|
+
provideContext,
|
|
8
|
+
effect,
|
|
9
|
+
untracked,
|
|
10
|
+
type ThemeState,
|
|
11
|
+
// product
|
|
12
|
+
ProductServiceContext,
|
|
13
|
+
createProductService,
|
|
14
|
+
createMockProductAdapter,
|
|
15
|
+
// customer
|
|
16
|
+
CustomerServiceContext,
|
|
17
|
+
createCustomerService,
|
|
18
|
+
createMockCustomerAdapter,
|
|
19
|
+
// orders
|
|
20
|
+
OrdersServiceContext,
|
|
21
|
+
createOrdersService,
|
|
22
|
+
createMockOrdersAdapter,
|
|
23
|
+
// subscription
|
|
24
|
+
SubscriptionServiceContext,
|
|
25
|
+
createSubscriptionService,
|
|
26
|
+
createMockSubscriptionAdapter,
|
|
27
|
+
// schedules
|
|
28
|
+
SchedulesServiceContext,
|
|
29
|
+
createSchedulesService,
|
|
30
|
+
createMockSchedulesAdapter,
|
|
31
|
+
// router
|
|
32
|
+
RouterServiceContext,
|
|
33
|
+
createMockRouterService,
|
|
34
|
+
// settings
|
|
35
|
+
SettingsServiceContext,
|
|
36
|
+
createSettingsService,
|
|
37
|
+
// shop
|
|
38
|
+
ShopServiceContext,
|
|
39
|
+
createShopService,
|
|
40
|
+
// login
|
|
41
|
+
LoginServiceContext,
|
|
42
|
+
// workflow
|
|
43
|
+
WorkflowServiceContext,
|
|
44
|
+
createWorkflowService,
|
|
45
|
+
createMockWorkflowApiAdapter,
|
|
46
|
+
} from "@juo/blocks";
|
|
47
|
+
import { createMockLoginService } from "./mock-login";
|
|
48
|
+
import { createWorkflowOverlay } from "./workflowOverlay";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Provide every available blocks service (backed by mock adapters) onto the
|
|
52
|
+
* dev preview's context root, and mount the workflow overlay.
|
|
53
|
+
*/
|
|
54
|
+
export function provideMockServices(root: HTMLElement, themeState: ThemeState) {
|
|
55
|
+
// One product adapter instance, shared with the subscription mock so that
|
|
56
|
+
// items added to a subscription resolve a real title/price from the same
|
|
57
|
+
// product catalog the picker reads from.
|
|
58
|
+
const productAdapter = createMockProductAdapter();
|
|
59
|
+
provideContext(
|
|
60
|
+
root,
|
|
61
|
+
ProductServiceContext,
|
|
62
|
+
createProductService(productAdapter)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const customerService = createCustomerService({
|
|
66
|
+
adapter: createMockCustomerAdapter(),
|
|
67
|
+
getAccountOrdersUrl: () => "https://example.com/account",
|
|
68
|
+
});
|
|
69
|
+
// Eagerly populate `current` so name-aware blocks (e.g. Header) render in the
|
|
70
|
+
// dev preview. `createCustomerService` leaves `current` as `null` until
|
|
71
|
+
// `getCurrent()` is called — in the portal that population is done by
|
|
72
|
+
// `useCustomerService`'s watchEffect; here nothing else triggers it.
|
|
73
|
+
void customerService.getCurrent();
|
|
74
|
+
provideContext(root, CustomerServiceContext, customerService);
|
|
75
|
+
|
|
76
|
+
provideContext(
|
|
77
|
+
root,
|
|
78
|
+
OrdersServiceContext,
|
|
79
|
+
createOrdersService(createMockOrdersAdapter())
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// One subscription adapter instance, shared with the schedules mock so that
|
|
83
|
+
// item add/remove/update mutations are reflected in the projected schedule.
|
|
84
|
+
const subscriptionAdapter = createMockSubscriptionAdapter({ productAdapter });
|
|
85
|
+
const subscriptionService = createSubscriptionService(subscriptionAdapter);
|
|
86
|
+
provideContext(root, SubscriptionServiceContext, subscriptionService);
|
|
87
|
+
|
|
88
|
+
const schedulesService = createSchedulesService(
|
|
89
|
+
createMockSchedulesAdapter(subscriptionAdapter),
|
|
90
|
+
{
|
|
91
|
+
// Share the billing-attempt signal so schedule-aware blocks stay in sync.
|
|
92
|
+
hasPendingBilling: subscriptionService.hasPendingBilling,
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
provideContext(root, SchedulesServiceContext, schedulesService);
|
|
96
|
+
|
|
97
|
+
provideContext(root, RouterServiceContext, createMockRouterService());
|
|
98
|
+
|
|
99
|
+
provideContext(root, SettingsServiceContext, createSettingsService());
|
|
100
|
+
|
|
101
|
+
provideContext(
|
|
102
|
+
root,
|
|
103
|
+
ShopServiceContext,
|
|
104
|
+
createShopService({ getContactEmail: () => "support@example.com" })
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
provideContext(root, LoginServiceContext, createMockLoginService());
|
|
108
|
+
|
|
109
|
+
// Note: OverlayServiceContext is provided by the theme shell's setup
|
|
110
|
+
// functions — editor mode must pass the same overlay service instance into
|
|
111
|
+
// `setupEditorMode`, so its lifecycle is owned there, not here.
|
|
112
|
+
|
|
113
|
+
const workflowService = createWorkflowService({
|
|
114
|
+
api: createMockWorkflowApiAdapter(),
|
|
115
|
+
themeState,
|
|
116
|
+
});
|
|
117
|
+
provideContext(root, WorkflowServiceContext, workflowService);
|
|
118
|
+
|
|
119
|
+
// Mount the workflow overlay UI so flows render in the dev preview.
|
|
120
|
+
createWorkflowOverlay(root, workflowService, themeState);
|
|
121
|
+
|
|
122
|
+
// Seed a focused subscription so subscription-scoped blocks render. There is
|
|
123
|
+
// no subscription id in the dev-preview URL, so use the mock adapter's default
|
|
124
|
+
// (mirrors the editor seed in customer-portal's useSubscriptionService).
|
|
125
|
+
void (async () => {
|
|
126
|
+
const idResult = await subscriptionService.getDefaultSubscriptionId();
|
|
127
|
+
const id = idResult._tag === "Success" ? idResult.data : null;
|
|
128
|
+
if (!id) return;
|
|
129
|
+
const subResult = await subscriptionService.getById(id);
|
|
130
|
+
if (subResult._tag === "Success") {
|
|
131
|
+
subscriptionService.current.value = subResult.data;
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
|
|
135
|
+
// Cascade: load the upcoming order whenever the focused subscription changes
|
|
136
|
+
// (mirrors customer-portal's useSchedulesService watcher).
|
|
137
|
+
effect(() => {
|
|
138
|
+
const id = subscriptionService.current.value?.id ?? null;
|
|
139
|
+
untracked(() => {
|
|
140
|
+
if (!id) {
|
|
141
|
+
schedulesService.current.value = null;
|
|
142
|
+
schedulesService.currentLoading.value = false;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
void schedulesService.reloadCurrent(id);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Re-project the schedule after any item add/remove/update. The focused
|
|
150
|
+
// subscription id is unchanged by an item mutation, so the id-based watcher
|
|
151
|
+
// above does not fire — `itemsChangedAt` bumps on every successful change.
|
|
152
|
+
effect(() => {
|
|
153
|
+
subscriptionService.itemsChangedAt.value; // subscribe
|
|
154
|
+
untracked(() => {
|
|
155
|
+
const id = subscriptionService.current.value?.id ?? null;
|
|
156
|
+
if (id) void schedulesService.reloadCurrent(id);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// source tree contains only their blocks. Vite serves this file directly (see
|
|
3
3
|
// the <script> + import map injected in index.html); it never enters the built
|
|
4
4
|
// blocks bundle (whose entry is `src/blocks/register.ts`).
|
|
5
|
-
import "@juo/blocks/web-components/editor";
|
|
6
5
|
{% if tailwind %}import "../src/tailwind.css";
|
|
7
6
|
{% else %}import "../src/styles.css";
|
|
8
7
|
{% endif %}
|
|
@@ -19,15 +18,38 @@ import {
|
|
|
19
18
|
ThemeStateContext,
|
|
20
19
|
TranslationContext,
|
|
21
20
|
createTranslationService,
|
|
21
|
+
createOverlayService,
|
|
22
|
+
OverlayServiceContext,
|
|
22
23
|
untracked,
|
|
23
24
|
} from "@juo/blocks";
|
|
24
25
|
import { registerBlocks as registerProjectBlocks } from "../src/main";
|
|
26
|
+
import { provideMockServices } from "./mock-services";
|
|
25
27
|
// Native blocks shipped by @juo/blocks-extensions, loaded from CDN. Its bare
|
|
26
28
|
// "@juo/blocks" import is bridged to the local instance via the index.html
|
|
27
29
|
// import map (see vite/blocks-shim.ts).
|
|
28
30
|
// @ts-expect-error Remote, untyped module
|
|
29
31
|
import { blocks as nativeBlocks } from "https://cdn.juo.io/web/blocks/juo/{{ extensionsVersion }}/extensions.js";
|
|
30
32
|
|
|
33
|
+
function hexToOklch(hex: string): { l: number; h: number; c: number } {
|
|
34
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
35
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
36
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
37
|
+
const linearize = (v: number) =>
|
|
38
|
+
v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
39
|
+
const lr = linearize(r);
|
|
40
|
+
const lg = linearize(g);
|
|
41
|
+
const lb = linearize(b);
|
|
42
|
+
const lm = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
|
|
43
|
+
const mm = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
|
|
44
|
+
const sm = Math.cbrt(0.0883024619 * lr + 0.2220049178 * lg + 0.6896926208 * lb);
|
|
45
|
+
const L = 0.2104542553 * lm + 0.793617785 * mm - 0.0040720468 * sm;
|
|
46
|
+
const a = 1.9779984951 * lm - 2.428592205 * mm + 0.4505937099 * sm;
|
|
47
|
+
const bOk = 0.0259040371 * lm + 0.7827717662 * mm - 0.808675766 * sm;
|
|
48
|
+
const c = Math.sqrt(a * a + bOk * bOk);
|
|
49
|
+
const h = Math.atan2(bOk, a) * (180 / Math.PI);
|
|
50
|
+
return { l: L * 100, h: h < 0 ? h + 360 : h, c };
|
|
51
|
+
}
|
|
52
|
+
|
|
31
53
|
const homePageBlock = defineBlock("HomePage", {
|
|
32
54
|
displayName: "Home page",
|
|
33
55
|
group: "page",
|
|
@@ -51,7 +73,15 @@ const homePageBlock = defineBlock("HomePage", {
|
|
|
51
73
|
initialValue: () => ({
|
|
52
74
|
props: {},
|
|
53
75
|
slots: {
|
|
54
|
-
main: [
|
|
76
|
+
main: [
|
|
77
|
+
createBlockInstance("Banner"),
|
|
78
|
+
createBlockInstance("Header"),
|
|
79
|
+
createBlockInstance("RenewalSummary"),
|
|
80
|
+
createBlockInstance("UpcomingOrderItems"),
|
|
81
|
+
createBlockInstance("Starter Block"),
|
|
82
|
+
createBlockInstance("CustomerSubscriptions"),
|
|
83
|
+
createBlockInstance("OrderList"),
|
|
84
|
+
],
|
|
55
85
|
},
|
|
56
86
|
}),
|
|
57
87
|
renderer() {
|
|
@@ -89,7 +119,9 @@ async function setupEditorMode(root: HTMLElement) {
|
|
|
89
119
|
routerService,
|
|
90
120
|
});
|
|
91
121
|
provideContext(root, ThemeStateContext, themeState);
|
|
92
|
-
|
|
122
|
+
const overlayService = createOverlayService(themeState);
|
|
123
|
+
provideContext(root, OverlayServiceContext, overlayService);
|
|
124
|
+
await editorModule.setupEditorMode({ routerService, themeState, overlayService });
|
|
93
125
|
}
|
|
94
126
|
|
|
95
127
|
function setupViewMode(root: HTMLElement) {
|
|
@@ -99,11 +131,18 @@ function setupViewMode(root: HTMLElement) {
|
|
|
99
131
|
}),
|
|
100
132
|
});
|
|
101
133
|
provideContext(root, ThemeStateContext, themeState);
|
|
134
|
+
provideContext(root, OverlayServiceContext, createOverlayService(themeState));
|
|
102
135
|
}
|
|
103
136
|
|
|
104
137
|
async function initializeTheme() {
|
|
105
138
|
const root = document.querySelector("juo-context-root") as HTMLElement;
|
|
106
139
|
|
|
140
|
+
if (isEditorPreview()) {
|
|
141
|
+
await import("@juo/blocks/web-components/editor");
|
|
142
|
+
} else {
|
|
143
|
+
await import("@juo/blocks/web-components/runtime");
|
|
144
|
+
}
|
|
145
|
+
|
|
107
146
|
registerBlocks();
|
|
108
147
|
|
|
109
148
|
if (isEditorPreview()) {
|
|
@@ -114,6 +153,89 @@ async function initializeTheme() {
|
|
|
114
153
|
|
|
115
154
|
const themeState = injectContext(root, ThemeStateContext);
|
|
116
155
|
|
|
156
|
+
effect(() => {
|
|
157
|
+
const styles = themeState.globalStyles.value;
|
|
158
|
+
const el = root;
|
|
159
|
+
|
|
160
|
+
el.setAttribute("data-volume", styles.volume);
|
|
161
|
+
el.setAttribute("data-style", styles.style);
|
|
162
|
+
el.setAttribute("data-corners", styles.corners);
|
|
163
|
+
el.setAttribute("data-button-corners", styles.buttonCorners);
|
|
164
|
+
el.setAttribute("data-thumbnail-corners", styles.thumbnailCorners);
|
|
165
|
+
|
|
166
|
+
const bg = styles.colors.background;
|
|
167
|
+
if (bg) {
|
|
168
|
+
el.style.setProperty("background-color", bg);
|
|
169
|
+
} else {
|
|
170
|
+
el.style.removeProperty("background-color");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const colorizableKeys = ["tint", "accent", "secondary", "callout"] as const;
|
|
174
|
+
|
|
175
|
+
if (styles.theme === "custom") {
|
|
176
|
+
for (const key of colorizableKeys) {
|
|
177
|
+
el.setAttribute(`data-${key}`, "custom");
|
|
178
|
+
const hex = styles.colors[key];
|
|
179
|
+
if (hex) {
|
|
180
|
+
const { l, h, c } = hexToOklch(hex);
|
|
181
|
+
el.style.setProperty(`--${key}-l`, `${l}%`);
|
|
182
|
+
el.style.setProperty(`--${key}-hue`, String(h));
|
|
183
|
+
el.style.setProperty(`--${key}-chroma`, String(c / 0.045));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
el.setAttribute("data-tint", styles.themeType ?? "neutral");
|
|
188
|
+
el.removeAttribute("data-callout");
|
|
189
|
+
|
|
190
|
+
const customColorKeys = ["accent", "secondary"] as const;
|
|
191
|
+
for (const key of customColorKeys) {
|
|
192
|
+
const hex = styles.colors[key];
|
|
193
|
+
if (hex) {
|
|
194
|
+
el.setAttribute(`data-${key}`, "custom");
|
|
195
|
+
const { l, h, c } = hexToOklch(hex);
|
|
196
|
+
el.style.setProperty(`--${key}-l`, `${l}%`);
|
|
197
|
+
el.style.setProperty(`--${key}-hue`, String(h));
|
|
198
|
+
el.style.setProperty(`--${key}-chroma`, String(c / 0.045));
|
|
199
|
+
} else {
|
|
200
|
+
el.removeAttribute(`data-${key}`);
|
|
201
|
+
el.style.removeProperty(`--${key}-l`);
|
|
202
|
+
el.style.removeProperty(`--${key}-hue`);
|
|
203
|
+
el.style.removeProperty(`--${key}-chroma`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
el.style.removeProperty("--tint-l");
|
|
208
|
+
el.style.removeProperty("--tint-hue");
|
|
209
|
+
el.style.removeProperty("--tint-chroma");
|
|
210
|
+
el.style.removeProperty("--callout-l");
|
|
211
|
+
el.style.removeProperty("--callout-hue");
|
|
212
|
+
el.style.removeProperty("--callout-chroma");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const semanticColorMap = [
|
|
216
|
+
{ themeKey: "success", cssPrefix: "success" },
|
|
217
|
+
{ themeKey: "error", cssPrefix: "error" },
|
|
218
|
+
{ themeKey: "information", cssPrefix: "info" },
|
|
219
|
+
{ themeKey: "warning", cssPrefix: "warning" },
|
|
220
|
+
] as const;
|
|
221
|
+
|
|
222
|
+
for (const { themeKey, cssPrefix } of semanticColorMap) {
|
|
223
|
+
const hex = styles.colors[themeKey];
|
|
224
|
+
if (hex) {
|
|
225
|
+
el.setAttribute(`data-${cssPrefix}`, "custom");
|
|
226
|
+
const { l, h, c } = hexToOklch(hex);
|
|
227
|
+
el.style.setProperty(`--${cssPrefix}-l`, `${l}%`);
|
|
228
|
+
el.style.setProperty(`--${cssPrefix}-hue`, String(h));
|
|
229
|
+
el.style.setProperty(`--${cssPrefix}-chroma`, String(c / 0.045));
|
|
230
|
+
} else {
|
|
231
|
+
el.removeAttribute(`data-${cssPrefix}`);
|
|
232
|
+
el.style.removeProperty(`--${cssPrefix}-l`);
|
|
233
|
+
el.style.removeProperty(`--${cssPrefix}-hue`);
|
|
234
|
+
el.style.removeProperty(`--${cssPrefix}-chroma`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
117
239
|
await themeState.locales.setLocale("en");
|
|
118
240
|
provideContext(
|
|
119
241
|
root,
|
|
@@ -125,6 +247,10 @@ async function initializeTheme() {
|
|
|
125
247
|
}),
|
|
126
248
|
);
|
|
127
249
|
|
|
250
|
+
// Provide every blocks service (backed by mock adapters) so authored blocks
|
|
251
|
+
// have their service dependencies available in the dev preview.
|
|
252
|
+
provideMockServices(root, themeState);
|
|
253
|
+
|
|
128
254
|
void themeState.resolve("page", "/");
|
|
129
255
|
|
|
130
256
|
effect(() => {
|