@webround/editor 0.0.0-stage → 0.3.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.
Files changed (227) hide show
  1. package/dist/blocks.d.ts +90 -0
  2. package/dist/blocks.js +119 -0
  3. package/dist/bridge.d.ts +96 -0
  4. package/dist/bridge.js +970 -0
  5. package/dist/check/aria.d.ts +19 -0
  6. package/dist/check/aria.js +106 -0
  7. package/dist/check/cli.d.ts +3 -0
  8. package/dist/check/cli.js +30 -0
  9. package/dist/check/css.d.ts +13 -0
  10. package/dist/check/css.js +63 -0
  11. package/dist/check/html.d.ts +8 -0
  12. package/dist/check/html.js +29 -0
  13. package/dist/check/index.d.ts +4 -0
  14. package/dist/check/index.js +5 -0
  15. package/dist/check/links.d.ts +7 -0
  16. package/dist/check/links.js +23 -0
  17. package/dist/check/orphans.d.ts +20 -0
  18. package/dist/check/orphans.js +57 -0
  19. package/dist/check/producer-code.d.ts +53 -0
  20. package/dist/check/producer-code.js +379 -0
  21. package/dist/check/producer-dom.d.ts +21 -0
  22. package/dist/check/producer-dom.js +52 -0
  23. package/dist/check/project.d.ts +20 -0
  24. package/dist/check/project.js +51 -0
  25. package/dist/check/rules.d.ts +7 -0
  26. package/dist/check/rules.js +517 -0
  27. package/dist/check/types.d.ts +160 -0
  28. package/dist/check/types.js +22 -0
  29. package/dist/components/WrAltcha.d.ts +5 -0
  30. package/dist/components/WrAltcha.js +8 -0
  31. package/dist/components/WrButton.d.ts +8 -0
  32. package/dist/components/WrButton.js +34 -0
  33. package/dist/components/WrForm.d.ts +6 -0
  34. package/dist/components/WrForm.js +29 -0
  35. package/dist/components/WrFormPrivacy.d.ts +4 -0
  36. package/dist/components/WrFormPrivacy.js +7 -0
  37. package/dist/components/WrFormSuccess.d.ts +4 -0
  38. package/dist/components/WrFormSuccess.js +12 -0
  39. package/dist/components/WrIframe.d.ts +12 -0
  40. package/dist/components/WrIframe.js +30 -0
  41. package/dist/components/WrImage.d.ts +9 -0
  42. package/dist/components/WrImage.js +28 -0
  43. package/dist/components/WrInput.d.ts +8 -0
  44. package/dist/components/WrInput.js +29 -0
  45. package/dist/components/WrLink.d.ts +10 -0
  46. package/dist/components/WrLink.js +38 -0
  47. package/dist/components/WrNav.d.ts +26 -0
  48. package/dist/components/WrNav.js +30 -0
  49. package/dist/components/WrSection.d.ts +11 -0
  50. package/dist/components/WrSection.js +5 -0
  51. package/dist/components/WrShared.d.ts +12 -0
  52. package/dist/components/WrShared.js +20 -0
  53. package/dist/components/WrText.d.ts +11 -0
  54. package/dist/components/WrText.js +41 -0
  55. package/dist/components/WrVideo.d.ts +9 -0
  56. package/dist/components/WrVideo.js +73 -0
  57. package/dist/components/text-editor/CodeThemeContext.d.ts +8 -0
  58. package/dist/components/text-editor/CodeThemeContext.js +10 -0
  59. package/dist/components/text-editor/ExampleTheme.d.ts +77 -0
  60. package/dist/components/text-editor/ExampleTheme.js +76 -0
  61. package/dist/components/text-editor/ToolbarContext.d.ts +17 -0
  62. package/dist/components/text-editor/ToolbarContext.js +32 -0
  63. package/dist/components/text-editor/WrTextEditor.d.ts +13 -0
  64. package/dist/components/text-editor/WrTextEditor.js +48 -0
  65. package/dist/components/text-editor/common/constants.d.ts +109 -0
  66. package/dist/components/text-editor/common/constants.js +58 -0
  67. package/dist/components/text-editor/common/surface.d.ts +3 -0
  68. package/dist/components/text-editor/common/surface.js +8 -0
  69. package/dist/components/text-editor/common/utils.d.ts +7 -0
  70. package/dist/components/text-editor/common/utils.js +78 -0
  71. package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.d.ts +5 -0
  72. package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.js +41 -0
  73. package/dist/components/text-editor/hooks/useLexicalMarkdown.d.ts +33 -0
  74. package/dist/components/text-editor/hooks/useLexicalMarkdown.js +284 -0
  75. package/dist/components/text-editor/nodes/CodeBlockNode.d.ts +23 -0
  76. package/dist/components/text-editor/nodes/CodeBlockNode.js +138 -0
  77. package/dist/components/text-editor/nodes/HorizontalRuleNode.d.ts +13 -0
  78. package/dist/components/text-editor/nodes/HorizontalRuleNode.js +22 -0
  79. package/dist/components/text-editor/plugins/CodeBlockPlugin.d.ts +2 -0
  80. package/dist/components/text-editor/plugins/CodeBlockPlugin.js +38 -0
  81. package/dist/components/text-editor/plugins/FontPlugin.d.ts +12 -0
  82. package/dist/components/text-editor/plugins/FontPlugin.js +114 -0
  83. package/dist/components/text-editor/plugins/FontSizePlugin.d.ts +7 -0
  84. package/dist/components/text-editor/plugins/FontSizePlugin.js +105 -0
  85. package/dist/components/text-editor/plugins/FontWeightPlugin.d.ts +6 -0
  86. package/dist/components/text-editor/plugins/FontWeightPlugin.js +103 -0
  87. package/dist/components/text-editor/plugins/HTMLTagPlugin.d.ts +7 -0
  88. package/dist/components/text-editor/plugins/HTMLTagPlugin.js +82 -0
  89. package/dist/components/text-editor/plugins/HorizontalRulePlugin.d.ts +2 -0
  90. package/dist/components/text-editor/plugins/HorizontalRulePlugin.js +35 -0
  91. package/dist/components/text-editor/plugins/LineSpacingPlugin.d.ts +7 -0
  92. package/dist/components/text-editor/plugins/LineSpacingPlugin.js +83 -0
  93. package/dist/components/text-editor/plugins/LinkPlugin.d.ts +8 -0
  94. package/dist/components/text-editor/plugins/LinkPlugin.js +183 -0
  95. package/dist/components/text-editor/plugins/ListToolbarPlugin.d.ts +7 -0
  96. package/dist/components/text-editor/plugins/ListToolbarPlugin.js +40 -0
  97. package/dist/components/text-editor/plugins/LoadContentPlugin.d.ts +4 -0
  98. package/dist/components/text-editor/plugins/LoadContentPlugin.js +63 -0
  99. package/dist/components/text-editor/plugins/MarkdownExportPlugin.d.ts +4 -0
  100. package/dist/components/text-editor/plugins/MarkdownExportPlugin.js +17 -0
  101. package/dist/components/text-editor/plugins/MarkdownPastePlugin.d.ts +2 -0
  102. package/dist/components/text-editor/plugins/MarkdownPastePlugin.js +45 -0
  103. package/dist/components/text-editor/plugins/SanitizePastePlugin.d.ts +3 -0
  104. package/dist/components/text-editor/plugins/SanitizePastePlugin.js +40 -0
  105. package/dist/components/text-editor/plugins/TextAlignPlugin.d.ts +7 -0
  106. package/dist/components/text-editor/plugins/TextAlignPlugin.js +65 -0
  107. package/dist/components/text-editor/plugins/TextColorPlugin.d.ts +7 -0
  108. package/dist/components/text-editor/plugins/TextColorPlugin.js +117 -0
  109. package/dist/components/text-editor/plugins/ToolbarPlugin.d.ts +10 -0
  110. package/dist/components/text-editor/plugins/ToolbarPlugin.js +73 -0
  111. package/dist/components/text-editor/theme-colors.d.ts +11 -0
  112. package/dist/components/text-editor/theme-colors.js +28 -0
  113. package/dist/context/WrAppContext.d.ts +35 -0
  114. package/dist/context/WrAppContext.js +113 -0
  115. package/dist/context/WrLayout.d.ts +15 -0
  116. package/dist/context/WrLayout.js +22 -0
  117. package/dist/context/WrPageContext.d.ts +28 -0
  118. package/dist/context/WrPageContext.js +282 -0
  119. package/dist/context/restoreValues.d.ts +2 -0
  120. package/dist/context/restoreValues.js +6 -0
  121. package/dist/contrast.d.ts +45 -0
  122. package/dist/contrast.js +122 -0
  123. package/dist/drop.d.ts +7 -0
  124. package/dist/drop.js +51 -0
  125. package/dist/gaps.d.ts +14 -0
  126. package/dist/gaps.js +50 -0
  127. package/dist/hooks/useWrMessage.d.ts +5 -0
  128. package/dist/hooks/useWrMessage.js +24 -0
  129. package/dist/hooks/useWrSave.d.ts +2 -0
  130. package/dist/hooks/useWrSave.js +11 -0
  131. package/dist/index.css +467 -0
  132. package/dist/index.d.ts +20 -0
  133. package/dist/index.js +20 -0
  134. package/dist/interactive.d.ts +8 -0
  135. package/dist/interactive.js +13 -0
  136. package/dist/measure-rules.d.ts +34 -0
  137. package/dist/measure-rules.js +54 -0
  138. package/dist/measure.d.ts +11 -0
  139. package/dist/measure.js +139 -0
  140. package/dist/messages.d.ts +860 -0
  141. package/dist/messages.js +2 -0
  142. package/dist/nav.d.ts +5 -0
  143. package/dist/nav.js +10 -0
  144. package/dist/operations/animations-store.d.ts +6 -0
  145. package/dist/operations/animations-store.js +63 -0
  146. package/dist/operations/cli.d.ts +3 -0
  147. package/dist/operations/cli.js +52 -0
  148. package/dist/operations/comments-store.d.ts +17 -0
  149. package/dist/operations/comments-store.js +62 -0
  150. package/dist/operations/context.d.ts +12 -0
  151. package/dist/operations/context.js +35 -0
  152. package/dist/operations/define.d.ts +81 -0
  153. package/dist/operations/define.js +86 -0
  154. package/dist/operations/index.d.ts +6 -0
  155. package/dist/operations/index.js +6 -0
  156. package/dist/operations/ops/check.d.ts +5 -0
  157. package/dist/operations/ops/check.js +53 -0
  158. package/dist/operations/ops/comments.d.ts +6 -0
  159. package/dist/operations/ops/comments.js +133 -0
  160. package/dist/operations/ops/content.d.ts +16 -0
  161. package/dist/operations/ops/content.js +73 -0
  162. package/dist/operations/ops/json.d.ts +4 -0
  163. package/dist/operations/ops/json.js +27 -0
  164. package/dist/operations/ops/langs.d.ts +4 -0
  165. package/dist/operations/ops/langs.js +32 -0
  166. package/dist/operations/ops/nodes.d.ts +8 -0
  167. package/dist/operations/ops/nodes.js +270 -0
  168. package/dist/operations/ops/pages.d.ts +8 -0
  169. package/dist/operations/ops/pages.js +154 -0
  170. package/dist/operations/ops/project.d.ts +36 -0
  171. package/dist/operations/ops/project.js +97 -0
  172. package/dist/operations/ops/seo.d.ts +17 -0
  173. package/dist/operations/ops/seo.js +358 -0
  174. package/dist/operations/ops/style.d.ts +76 -0
  175. package/dist/operations/ops/style.js +268 -0
  176. package/dist/operations/ops/stylesets.d.ts +9 -0
  177. package/dist/operations/ops/stylesets.js +145 -0
  178. package/dist/operations/ops/theme.d.ts +6 -0
  179. package/dist/operations/ops/theme.js +304 -0
  180. package/dist/operations/registry.d.ts +8 -0
  181. package/dist/operations/registry.js +19 -0
  182. package/dist/operations/stylesets-store.d.ts +50 -0
  183. package/dist/operations/stylesets-store.js +216 -0
  184. package/dist/registry.d.ts +5 -0
  185. package/dist/registry.js +24 -0
  186. package/dist/save.d.ts +65 -0
  187. package/dist/save.js +86 -0
  188. package/dist/schema.d.ts +69 -0
  189. package/dist/schema.js +110 -0
  190. package/dist/scope.d.ts +41 -0
  191. package/dist/scope.js +44 -0
  192. package/dist/vite-plugins/ast.d.ts +3 -0
  193. package/dist/vite-plugins/ast.js +238 -0
  194. package/dist/vite-plugins/core-swap.d.ts +3 -0
  195. package/dist/vite-plugins/core-swap.js +57 -0
  196. package/dist/vite-plugins/create-page.d.ts +4 -0
  197. package/dist/vite-plugins/create-page.js +59 -0
  198. package/dist/vite-plugins/guard.d.ts +15 -0
  199. package/dist/vite-plugins/guard.js +35 -0
  200. package/dist/vite-plugins/head.d.ts +13 -0
  201. package/dist/vite-plugins/head.js +53 -0
  202. package/dist/vite-plugins/index.d.ts +7 -0
  203. package/dist/vite-plugins/index.js +7 -0
  204. package/dist/vite-plugins/jsx-tree.d.ts +118 -0
  205. package/dist/vite-plugins/jsx-tree.js +686 -0
  206. package/dist/vite-plugins/langs.d.ts +3 -0
  207. package/dist/vite-plugins/langs.js +44 -0
  208. package/dist/vite-plugins/operations.d.ts +10 -0
  209. package/dist/vite-plugins/operations.js +66 -0
  210. package/dist/vite-plugins/pages-api.d.ts +3 -0
  211. package/dist/vite-plugins/pages-api.js +112 -0
  212. package/dist/vite-plugins/pages.d.ts +3 -0
  213. package/dist/vite-plugins/pages.js +64 -0
  214. package/dist/vite-plugins/read.d.ts +3 -0
  215. package/dist/vite-plugins/read.js +45 -0
  216. package/dist/vite-plugins/seo-files.d.ts +8 -0
  217. package/dist/vite-plugins/seo-files.js +42 -0
  218. package/dist/vite-plugins/theme.d.ts +3 -0
  219. package/dist/vite-plugins/theme.js +207 -0
  220. package/dist/vite-plugins/update.d.ts +3 -0
  221. package/dist/vite-plugins/update.js +46 -0
  222. package/dist/vite-plugins/wr-plugins.d.ts +15 -0
  223. package/dist/vite-plugins/wr-plugins.js +34 -0
  224. package/dist/vite-plugins.d.ts +11 -0
  225. package/dist/vite-plugins.js +518 -0
  226. package/package.json +96 -5
  227. package/README.md +0 -3
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // packages/editor/src/context/WrAppContext.tsx
3
+ import { createContext, useContext, useState, useCallback, useMemo, useEffect, useSyncExternalStore, lazy, Suspense } from 'react';
4
+ import { createBrowserRouter, RouterProvider, useLocation, useParams } from 'react-router-dom';
5
+ import { wrResolveRoutes, wrBuildRegistry, wrBuildLazyRegistry, wrBuildPageConfigMap, wrExpandRoutesForLangs, wrExtractLang, wrBuildLangPath, wrSharedScopes, } from '@webround/core/routing';
6
+ import { WrPageProvider } from './WrPageContext';
7
+ import { WrLayout } from './WrLayout';
8
+ import { WrSeoHead } from '@webround/core/seo-react';
9
+ import { WrNotFound } from '@webround/core/not-found';
10
+ import { setWrNavigate } from '../bridge';
11
+ const WrAppContext = createContext(null);
12
+ export function useWrApp() {
13
+ const ctx = useContext(WrAppContext);
14
+ return {
15
+ lang: ctx?.lang ?? 'default',
16
+ setLang: ctx?.setLang ?? (() => { }),
17
+ };
18
+ }
19
+ function WrDefaultPageFallback() {
20
+ return _jsx("div", { "data-wr-loading": "true" });
21
+ }
22
+ const lazyComponentCache = new Map();
23
+ function getLazyComponent(route) {
24
+ let cached = lazyComponentCache.get(route.routePath);
25
+ if (!cached) {
26
+ cached = lazy(route.loadComponent);
27
+ lazyComponentCache.set(route.routePath, cached);
28
+ }
29
+ return cached;
30
+ }
31
+ export const WrPagesConfigContext = createContext({});
32
+ /** Every shared scope of the site: the header, the footer and the shared components it declares (`config/shared.json`). */
33
+ const WrSharedScopesContext = createContext(wrSharedScopes());
34
+ export const useWrSharedScopes = () => useContext(WrSharedScopesContext);
35
+ /** What is the same for the whole site, for search engines: read live, like the pages' config, so changing it never rebuilds the router. */
36
+ const WrSeoSiteContext = createContext({});
37
+ function WrRouteElement({ route, lazyFallback: LazyFallback, Header, Footer, }) {
38
+ const pageConfigMap = useContext(WrPagesConfigContext); // letto live, non più congelato nel router
39
+ const { site, langs } = useContext(WrSeoSiteContext);
40
+ const params = useParams();
41
+ const { pathname } = useLocation();
42
+ const { lang } = useWrApp();
43
+ // `/:lang/…` answers for any word in that place: only the languages of the site are one
44
+ if (langs && params.lang !== undefined && !langs.available.includes(params.lang))
45
+ return _jsx(WrNotFound, { langs: langs });
46
+ const pageName = route.resolvePageName(params);
47
+ const config = pageConfigMap[pageName] ?? {};
48
+ const file = `pages/${route.component}.tsx`;
49
+ const body = route.lazy
50
+ ? (() => {
51
+ const LazyComponent = getLazyComponent(route);
52
+ return _jsx(Suspense, { fallback: _jsx(LazyFallback, {}), children: _jsx(LazyComponent, {}) });
53
+ })()
54
+ : (() => {
55
+ const Component = route.Component;
56
+ return _jsx(Component, {});
57
+ })();
58
+ return (_jsxs(WrPageProvider, { config: config, pageName: pageName, file: file, children: [_jsx(WrSeoHead, { site: site, config: config, pathname: pathname, lang: lang, langs: langs }), _jsx(WrLayout, { Header: Header, Footer: Footer, showHeader: route.header, showFooter: route.footer, children: body })] }, pageName));
59
+ }
60
+ export function WrAppProvider({ routesFile, eagerModules, lazyModules, configModules, langs, initialLang = 'default', lazyFallback = WrDefaultPageFallback, Header, Footer, seo, shared, children, }) {
61
+ // Config delle pagine: stato live, indipendente dal router.
62
+ // Si aggiorna ogni volta che cambia la reference di `configModules`
63
+ // (es. via HMR in main.tsx), senza mai toccare il router.
64
+ const [pageConfigMap, setPageConfigMap] = useState(() => wrBuildPageConfigMap(configModules));
65
+ useEffect(() => {
66
+ setPageConfigMap(wrBuildPageConfigMap(configModules));
67
+ }, [configModules]);
68
+ // Router: NIENTE configModules qui dentro. Si ricostruisce solo se
69
+ // cambia la struttura delle rotte/componenti, mai per un content edit.
70
+ const router = useMemo(() => {
71
+ const registry = wrBuildRegistry(eagerModules);
72
+ const lazyRegistry = wrBuildLazyRegistry(lazyModules);
73
+ const { routes, errors } = wrResolveRoutes(routesFile, registry, lazyRegistry);
74
+ if (errors.missingComponents.length > 0) {
75
+ console.error('[wr] unresolved route -> component mappings:', errors.missingComponents);
76
+ }
77
+ const expandedRoutes = wrExpandRoutesForLangs(routes, langs);
78
+ const builtRouter = createBrowserRouter([
79
+ ...expandedRoutes.map((route) => ({
80
+ path: route.pattern,
81
+ element: _jsx(WrRouteElement, { route: route, lazyFallback: lazyFallback, Header: Header, Footer: Footer }),
82
+ })),
83
+ // An address that leads to no page takes the visitor to the home page (the preview shows what the site will do)
84
+ { path: '*', element: _jsx(WrNotFound, { langs: langs }) },
85
+ ]);
86
+ setWrNavigate(builtRouter.navigate);
87
+ return builtRouter;
88
+ }, [routesFile, eagerModules, lazyModules, langs, lazyFallback, Header, Footer]);
89
+ // Fallback quando non c'è alcuna config lingue: stato locale semplice.
90
+ const [staticLang, setStaticLang] = useState(initialLang);
91
+ // Con `langs`: la lingua è sempre derivata dal pathname corrente del router,
92
+ // letto in modo reattivo — questo componente sta SOPRA il <RouterProvider>, niente useLocation().
93
+ const pathname = useSyncExternalStore(router.subscribe, () => router.state.location.pathname, () => router.state.location.pathname);
94
+ const lang = langs ? wrExtractLang(pathname, langs) : staticLang;
95
+ // The page says what language it is in (screen readers pick their voice by it): the language shown, `default` being the site's own
96
+ const htmlLang = langs ? (lang === 'default' ? langs.default : lang) : undefined;
97
+ useEffect(() => {
98
+ if (htmlLang)
99
+ document.documentElement.lang = htmlLang;
100
+ }, [htmlLang]);
101
+ const setLang = useCallback((target) => {
102
+ if (langs) {
103
+ router.navigate(wrBuildLangPath(target, pathname, langs));
104
+ }
105
+ else {
106
+ setStaticLang(target);
107
+ }
108
+ }, [langs, pathname, router]);
109
+ const seoContext = useMemo(() => ({ site: seo, langs }), [seo, langs]);
110
+ const sharedScopes = useMemo(() => wrSharedScopes(shared), [shared]);
111
+ return (_jsx(WrAppContext.Provider, { value: { lang, setLang }, children: _jsx(WrPagesConfigContext.Provider, { value: pageConfigMap, children: _jsx(WrSeoSiteContext.Provider, { value: seoContext, children: _jsxs(WrSharedScopesContext.Provider, { value: sharedScopes, children: [children, _jsx(RouterProvider, { router: router })] }) }) }) }));
112
+ }
113
+ //# sourceMappingURL=WrAppContext.js.map
@@ -0,0 +1,15 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+ /**
3
+ * La pagina con il suo header e il suo footer (stessa API e stesso markup del core, più l'ambito per l'editor). Vive DENTRO il
4
+ * provider della pagina e decide al primo render, con i flag già risolti dalla rotta: niente effetti, niente stato, niente scatto.
5
+ * Ogni parte condivisa è avvolta in un `display: contents` che porta il suo ambito (file + JSON): non genera nessun riquadro, ma
6
+ * dice al bridge dove scrivere quando si tocca un suo elemento. Il contenuto della pagina sta in un `<main>` (come nel core): lo mette il layout.
7
+ */
8
+ export declare function WrLayout({ Header, Footer, showHeader, showFooter, children }: {
9
+ Header?: ComponentType;
10
+ Footer?: ComponentType;
11
+ showHeader: boolean;
12
+ showFooter: boolean;
13
+ children: ReactNode;
14
+ }): import("react").JSX.Element;
15
+ //# sourceMappingURL=WrLayout.d.ts.map
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { WR_SHARED_SCOPES } from '@webround/core/routing';
3
+ import { WrScopeProvider } from './WrPageContext';
4
+ import { wrScopeProps } from '../scope';
5
+ /**
6
+ * La pagina con il suo header e il suo footer (stessa API e stesso markup del core, più l'ambito per l'editor). Vive DENTRO il
7
+ * provider della pagina e decide al primo render, con i flag già risolti dalla rotta: niente effetti, niente stato, niente scatto.
8
+ * Ogni parte condivisa è avvolta in un `display: contents` che porta il suo ambito (file + JSON): non genera nessun riquadro, ma
9
+ * dice al bridge dove scrivere quando si tocca un suo elemento. Il contenuto della pagina sta in un `<main>` (come nel core): lo mette il layout.
10
+ */
11
+ export function WrLayout({ Header, Footer, showHeader, showFooter, children }) {
12
+ const header = showHeader && Header ? Header : null;
13
+ const footer = showFooter && Footer ? Footer : null;
14
+ if (!header && !footer)
15
+ return _jsx("main", { children: children });
16
+ const shared = (key, Component) => {
17
+ const scope = WR_SHARED_SCOPES[key];
18
+ return (_jsx("div", { style: { display: 'contents' }, ...wrScopeProps(scope), children: _jsx(WrScopeProvider, { config: {}, pageName: scope.name, children: _jsx(Component, {}) }) }));
19
+ };
20
+ return (_jsxs("div", { "data-wr-layout": true, style: { display: 'flex', flexDirection: 'column', minHeight: '100dvh' }, children: [header && shared('header', header), _jsx("main", { style: { flex: '1 0 auto' }, children: children }), footer && shared('footer', footer)] }));
21
+ }
22
+ //# sourceMappingURL=WrLayout.js.map
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import type { WrPageConfig, WrElementConfig } from '@webround/core/types';
3
+ export declare function WrPageProvider({ config: initialConfig, pageName, file, children }: {
4
+ config: WrPageConfig;
5
+ pageName: string;
6
+ file: string;
7
+ children: React.ReactNode;
8
+ }): React.JSX.Element;
9
+ /**
10
+ * Header e footer: il config è il LORO (config/pages/_shared/…), ma la cronologia è quella della pagina in cui si è. Nessuna cronologia
11
+ * né stato proprio: le modifiche entrano nella cronologia della pagina con il loro ambito e annulla/ripristina le applicano lì.
12
+ * Dentro, i componenti Wr* funzionano come in una pagina (`pageName` è il nome del JSON dell'ambito, quindi le scritture vanno lì).
13
+ */
14
+ export declare function WrScopeProvider({ config: _ignored, pageName: scopeName, children }: {
15
+ config: WrPageConfig;
16
+ pageName: string;
17
+ children: React.ReactNode;
18
+ }): React.JSX.Element;
19
+ export declare function useWrPage(id: string): WrElementConfig | undefined;
20
+ export declare function useWrPageUpdate(): (id: string, patch: Partial<WrElementConfig>, scope?: string) => void;
21
+ export declare function useWrPageMeta(): string;
22
+ export declare function useWrHistory(): {
23
+ undo: () => void;
24
+ redo: () => void;
25
+ canUndo: boolean;
26
+ canRedo: boolean;
27
+ };
28
+ //# sourceMappingURL=WrPageContext.d.ts.map
@@ -0,0 +1,282 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { valuesToRestore } from './restoreValues';
3
+ import { createContext, useContext, useEffect, useState, useCallback, useRef, useMemo } from 'react';
4
+ import { initWrBridge, postHistoryState, refreshSelection, setWrHistory, setWrPageFile, setWrPageName, setWrAstHistoryPush, setWrNodeHistoryPush, setWrMoveHistoryPush, setWrStyleHistoryPush, putNode, fetchNode, fetchStyle, restoreStyle, postToEditor, deleteNode, insertRawNode, moveNode, } from '../bridge';
5
+ import { setWrValue } from '../registry';
6
+ import { wrSavePage, wrSaveFile } from '../save';
7
+ import { useWrApp, useWrSharedScopes, WrPagesConfigContext } from './WrAppContext';
8
+ const WrPageContext = createContext(null);
9
+ export function WrPageProvider({ config: initialConfig, pageName, file, children }) {
10
+ const [history, setHistory] = useState([]);
11
+ const [cursor, setCursor] = useState(-1); // -1 = stato iniziale, nessun passo applicato
12
+ const cursorRef = useRef(-1);
13
+ useEffect(() => { cursorRef.current = cursor; }, [cursor]);
14
+ // Il config di ogni ambito = quello su disco + le voci json di quell'ambito fino al cursore. Pagina, header e footer
15
+ // condividono UNA cronologia (la pagina corrente): si azzera col cambio pagina, come sempre.
16
+ const pagesConfig = useContext(WrPagesConfigContext);
17
+ const initialFor = useCallback((name) => name === pageName ? initialConfig : (pagesConfig[name] ?? {}), [pageName, initialConfig, pagesConfig]);
18
+ const deriveConfig = useCallback((name, entries, upTo) => {
19
+ let result = initialFor(name);
20
+ for (let i = 0; i <= upTo; i++) {
21
+ const entry = entries[i];
22
+ if (entry?.kind === 'json' && entry.scope === name)
23
+ result = { ...result, [entry.elementId]: entry.nextValue };
24
+ }
25
+ return result;
26
+ }, [initialFor]);
27
+ const configFor = useCallback((name) => deriveConfig(name, history, cursor), [deriveConfig, history, cursor]);
28
+ const config = useMemo(() => configFor(pageName), [configFor, pageName]);
29
+ const configForRef = useRef(configFor);
30
+ useEffect(() => { configForRef.current = configFor; }, [configFor]);
31
+ const { lang } = useWrApp();
32
+ const sharedScopes = useWrSharedScopes();
33
+ const updateConfig = useCallback((id, patch, scope = pageName) => {
34
+ setHistory(prev => {
35
+ const c = cursorRef.current;
36
+ // config corrente fino a c, per calcolare prevValue e il merge del patch
37
+ const currentConfig = deriveConfig(scope, prev, c);
38
+ const current = currentConfig[id];
39
+ const merged = { ...current, ...patch };
40
+ if (patch.content && current?.content && typeof current.content === 'object') {
41
+ merged.content = { ...current.content, ...patch.content };
42
+ }
43
+ const next = prev.slice(0, c + 1);
44
+ next.push({
45
+ kind: 'json',
46
+ scope,
47
+ elementId: id,
48
+ prevValue: current,
49
+ nextValue: merged,
50
+ });
51
+ return next;
52
+ });
53
+ setCursor(c => c + 1);
54
+ }, [pageName, deriveConfig]);
55
+ // Applica UN passo di cronologia in una direzione. true = riuscito.
56
+ const applyHistoryEntry = useCallback(async (entry, direction) => {
57
+ const undoing = direction === 'undo';
58
+ if (entry.kind === 'ast') {
59
+ const ok = await putNode(entry.elementId, entry.key, undoing ? (entry.prevValue ?? null) : entry.nextValue, entry.scope);
60
+ if (!ok)
61
+ return false;
62
+ const { fields, values } = await fetchNode(entry.elementId, entry.scope);
63
+ postToEditor({ type: 'wr:node:get:response', elementId: entry.elementId, fields, values });
64
+ return true;
65
+ }
66
+ if (entry.kind === 'style') {
67
+ const ok = await restoreStyle(entry.elementId, undoing ? entry.prevClassName : entry.nextClassName, entry.scope);
68
+ if (!ok)
69
+ return false;
70
+ // Il pannello dello stile mostra le classi dell'elemento: gli si dice com'è adesso
71
+ postToEditor({ type: 'wr:style:get:response', ...(await fetchStyle(entry.elementId, entry.scope)) });
72
+ return true;
73
+ }
74
+ if (entry.kind === 'move') {
75
+ // Annullare = spostarlo di nuovo dov'era; ripristinare = di nuovo dov'è andato. Il posto sta nella voce, non nella pagina in cui ci si trova.
76
+ const place = undoing ? entry.from : entry.to;
77
+ const result = await moveNode(entry.elementId, place.anchor, place.mode, entry.scope);
78
+ return result.ok;
79
+ }
80
+ if (entry.kind === 'node') {
81
+ // undo di una delete e redo di un insert = reinserire; gli altri due = rimuovere
82
+ const reinsert = (entry.op === 'delete') === undoing;
83
+ if (reinsert) {
84
+ if (!entry.anchor) {
85
+ console.error('[wr] impossibile reinserire: nessuna ancora di posizione nota per', entry.elementIds);
86
+ return false;
87
+ }
88
+ // Gli stessi id di prima (uniqueIds off): se qualcuno li ha presi nel frattempo, il passo fallisce e la cronologia si ferma
89
+ const { ok } = await insertRawNode(entry.anchor, entry.anchorMode, entry.jsx, false, entry.scope);
90
+ if (!ok)
91
+ return false;
92
+ await wrSaveFile(`config/pages/${entry.scope.name}.json`, entry.values);
93
+ return true;
94
+ }
95
+ for (const elementId of entry.elementIds) {
96
+ // L'operazione toglie anche i valori JSON dei nodi cancellati
97
+ const result = await deleteNode(elementId, entry.scope);
98
+ if (!result || 'error' in result)
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ await wrSavePage(entry.scope, entry.elementId, undoing ? (entry.prevValue ?? {}) : entry.nextValue);
104
+ return true;
105
+ }, [pageName]);
106
+ // Registrato in bridge.ts: chiamato appena una insert/delete di nodo va a buon fine, PRIMA
107
+ // della pulizia del JSON lato server — per la delete legge dal config corrente (via ref,
108
+ // non chiusura stale) i valori da ripristinare con l'undo.
109
+ const pushNodeHistory = useCallback((entry) => {
110
+ const values = entry.op === 'insert'
111
+ ? { ...entry.jsonSeed }
112
+ : valuesToRestore(entry.removedIds ?? [], configForRef.current(entry.scope.name));
113
+ setHistory(prev => {
114
+ const c = cursorRef.current;
115
+ const next = prev.slice(0, c + 1);
116
+ next.push({
117
+ kind: 'node', scope: entry.scope, op: entry.op, elementIds: entry.elementIds, jsx: entry.jsx,
118
+ anchor: entry.anchor, anchorMode: entry.anchorMode, values,
119
+ });
120
+ return next;
121
+ });
122
+ setCursor(c => c + 1);
123
+ }, []);
124
+ // Undo/redo eseguono il passo (I/O) PRIMA di spostare il cursor, mai dentro un updater di
125
+ // setState. Se un passo fallisce il cursor resta dov'è e la cronologia si blocca: continuare
126
+ // dopo un passo fallito significherebbe applicare altri step su uno stato file/UI divergente.
127
+ const historyRef = useRef(history);
128
+ useEffect(() => { historyRef.current = history; }, [history]);
129
+ const busyRef = useRef(false);
130
+ const [blocked, setBlocked] = useState(false);
131
+ const moveCursor = useCallback((next) => {
132
+ cursorRef.current = next;
133
+ setCursor(next);
134
+ }, []);
135
+ const runStep = useCallback(async (direction) => {
136
+ if (busyRef.current || blocked)
137
+ return;
138
+ const c = cursorRef.current;
139
+ const index = direction === 'undo' ? c : c + 1;
140
+ const entry = historyRef.current[index];
141
+ if (!entry)
142
+ return;
143
+ busyRef.current = true;
144
+ try {
145
+ const ok = await applyHistoryEntry(entry, direction);
146
+ if (ok)
147
+ moveCursor(direction === 'undo' ? c - 1 : c + 1);
148
+ else
149
+ setBlocked(true);
150
+ }
151
+ catch (err) {
152
+ console.error(`[wr] ${direction} fallito, cronologia bloccata:`, err);
153
+ setBlocked(true);
154
+ }
155
+ finally {
156
+ busyRef.current = false;
157
+ }
158
+ }, [blocked, moveCursor, applyHistoryEntry]);
159
+ const undo = useCallback(() => { void runStep('undo'); }, [runStep]);
160
+ const redo = useCallback(() => { void runStep('redo'); }, [runStep]);
161
+ const pushAstHistory = useCallback((elementId, key, prevValue, nextValue, scope) => {
162
+ setHistory(prev => {
163
+ const c = cursorRef.current;
164
+ const next = prev.slice(0, c + 1);
165
+ next.push({ kind: 'ast', scope, elementId, key, prevValue, nextValue });
166
+ return next;
167
+ });
168
+ setCursor(c => c + 1);
169
+ }, []);
170
+ useEffect(() => {
171
+ setWrAstHistoryPush(pushAstHistory);
172
+ return () => setWrAstHistoryPush(null);
173
+ }, [pushAstHistory]);
174
+ const pushStyleHistory = useCallback((elementId, prevClassName, nextClassName, scope) => {
175
+ setHistory(prev => {
176
+ const c = cursorRef.current;
177
+ const next = prev.slice(0, c + 1);
178
+ next.push({ kind: 'style', scope, elementId, prevClassName, nextClassName });
179
+ return next;
180
+ });
181
+ setCursor(c => c + 1);
182
+ }, []);
183
+ useEffect(() => {
184
+ setWrStyleHistoryPush(pushStyleHistory);
185
+ return () => setWrStyleHistoryPush(null);
186
+ }, [pushStyleHistory]);
187
+ const pushMoveHistory = useCallback((entry) => {
188
+ setHistory(prev => {
189
+ const c = cursorRef.current;
190
+ const next = prev.slice(0, c + 1);
191
+ next.push({ kind: 'move', ...entry });
192
+ return next;
193
+ });
194
+ setCursor(c => c + 1);
195
+ }, []);
196
+ useEffect(() => {
197
+ setWrMoveHistoryPush(pushMoveHistory);
198
+ return () => setWrMoveHistoryPush(null);
199
+ }, [pushMoveHistory]);
200
+ useEffect(() => {
201
+ setWrNodeHistoryPush(pushNodeHistory);
202
+ return () => setWrNodeHistoryPush(null);
203
+ }, [pushNodeHistory]);
204
+ useEffect(() => {
205
+ // Origin del parent (wr-editor) che può parlare con questo iframe, da env del sito: se manca
206
+ // il bridge non parte (fail closed) invece di accettare messaggi da chiunque.
207
+ const editorOrigin = import.meta.env?.VITE_WR_EDITOR_ORIGIN;
208
+ if (!editorOrigin) {
209
+ console.warn('[wr] VITE_WR_EDITOR_ORIGIN non definita: bridge con l\'editor disattivato');
210
+ return;
211
+ }
212
+ return initWrBridge(editorOrigin);
213
+ }, []);
214
+ useEffect(() => {
215
+ setWrHistory(undo, redo);
216
+ }, [undo, redo]);
217
+ useEffect(() => {
218
+ // Gli elementi di header, footer e componenti condivisi hanno i loro valori nel registro come quelli della pagina
219
+ const all = [config, ...sharedScopes.map(scope => configFor(scope.name))];
220
+ for (const [id, element] of all.flatMap(c => Object.entries(c))) {
221
+ setWrValue(id, element, lang);
222
+ refreshSelection(id);
223
+ }
224
+ }, [config, configFor, sharedScopes, lang]);
225
+ useEffect(() => {
226
+ const onKey = (e) => {
227
+ const mod = e.metaKey || e.ctrlKey;
228
+ if (mod && !e.shiftKey && e.key === 'z') {
229
+ e.preventDefault();
230
+ undo();
231
+ }
232
+ if (mod && (e.key === 'y' || (e.shiftKey && e.key === 'z'))) {
233
+ e.preventDefault();
234
+ redo();
235
+ }
236
+ };
237
+ window.addEventListener('keydown', onKey);
238
+ return () => window.removeEventListener('keydown', onKey);
239
+ }, [undo, redo]);
240
+ useEffect(() => {
241
+ postHistoryState(!blocked && cursor >= 0, !blocked && cursor < history.length - 1);
242
+ }, [cursor, history.length, blocked]);
243
+ useEffect(() => {
244
+ setWrPageFile(file);
245
+ setWrPageName(pageName);
246
+ }, [file, pageName]);
247
+ return (_jsx(WrPageContext.Provider, { value: { config, updateConfig, configFor, pageName, undo, redo, canUndo: !blocked && cursor >= 0, canRedo: !blocked && cursor < history.length - 1 }, children: children }));
248
+ }
249
+ /**
250
+ * Header e footer: il config è il LORO (config/pages/_shared/…), ma la cronologia è quella della pagina in cui si è. Nessuna cronologia
251
+ * né stato proprio: le modifiche entrano nella cronologia della pagina con il loro ambito e annulla/ripristina le applicano lì.
252
+ * Dentro, i componenti Wr* funzionano come in una pagina (`pageName` è il nome del JSON dell'ambito, quindi le scritture vanno lì).
253
+ */
254
+ export function WrScopeProvider({ config: _ignored, pageName: scopeName, children }) {
255
+ const page = useContext(WrPageContext);
256
+ const value = useMemo(() => ({
257
+ ...page,
258
+ config: page.configFor(scopeName),
259
+ pageName: scopeName,
260
+ updateConfig: (id, patch) => page.updateConfig(id, patch, scopeName),
261
+ }), [page, scopeName]);
262
+ return _jsx(WrPageContext.Provider, { value: value, children: children });
263
+ }
264
+ export function useWrPage(id) {
265
+ return useContext(WrPageContext)?.config[id];
266
+ }
267
+ export function useWrPageUpdate() {
268
+ return useContext(WrPageContext).updateConfig;
269
+ }
270
+ export function useWrPageMeta() {
271
+ return useContext(WrPageContext).pageName;
272
+ }
273
+ export function useWrHistory() {
274
+ const ctx = useContext(WrPageContext);
275
+ return {
276
+ undo: ctx?.undo ?? (() => { }),
277
+ redo: ctx?.redo ?? (() => { }),
278
+ canUndo: ctx?.canUndo ?? false,
279
+ canRedo: ctx?.canRedo ?? false,
280
+ };
281
+ }
282
+ //# sourceMappingURL=WrPageContext.js.map
@@ -0,0 +1,2 @@
1
+ export declare function valuesToRestore(removedIds: readonly string[], config: Readonly<Record<string, unknown>>): Record<string, unknown>;
2
+ //# sourceMappingURL=restoreValues.d.ts.map
@@ -0,0 +1,6 @@
1
+ // I valori JSON da rimettere se si annulla una cancellazione: solo quelli che esistevano. Un elemento che nel JSON non aveva
2
+ // niente (la maggior parte dei contenitori) non ne deve ottenere uno vuoto tornando: il JSON deve contenere solo ciò che ha un valore.
3
+ export function valuesToRestore(removedIds, config) {
4
+ return Object.fromEntries(removedIds.filter(id => config[id] !== undefined).map(id => [id, config[id]]));
5
+ }
6
+ //# sourceMappingURL=restoreValues.js.map
@@ -0,0 +1,45 @@
1
+ export interface Rgb {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ }
6
+ export interface Rgba extends Rgb {
7
+ a: number;
8
+ }
9
+ /** Relative luminance, 0 (black) to 1 (white). */
10
+ export declare const luminance: ({ r, g, b }: Rgb) => number;
11
+ /** The contrast ratio of two colors, 1 (none) to 21 (black on white). */
12
+ export declare function contrastRatio(a: Rgb, b: Rgb): number;
13
+ /** A color with some transparency, laid over another one that may have some too. */
14
+ export declare function over(top: Rgba, under: Rgba): Rgba;
15
+ /** One element around the text: its background, and the opacity it (and all it holds) is shown with. */
16
+ export interface Layer {
17
+ background: Rgba;
18
+ opacity: number;
19
+ }
20
+ /**
21
+ * What a pixel looks like in the end, where `top` is painted (the text's color, or nothing at all to get the backdrop) inside these layers,
22
+ * the nearest first: each layer paints its background under what its children painted, and is then shown with its opacity (so a faded
23
+ * block fades its text and its background together); the page is white under all. Opacity counts: text in a block at 50% is half as dark.
24
+ */
25
+ export declare function flatten(top: Rgba, layers: readonly Layer[]): Rgb;
26
+ /** WCAG 2.1 AA (1.4.3): large text (24px, or 18.66px bold) needs 3:1; the rest needs 4.5:1. */
27
+ export declare const requiredRatio: (fontSizePx: number, fontWeight: number) => number;
28
+ export interface ContrastFinding {
29
+ /** `data-wr-id` of the nearest element that can be edited. */
30
+ elementId: string;
31
+ ratio: number;
32
+ required: number;
33
+ /** The words that are hard to read (the start of them). */
34
+ text: string;
35
+ }
36
+ /**
37
+ * Looks at the text that is on the page now, with the colors the browser computed (the theme, the breakpoint and the dark mode are all in
38
+ * them), and says which has too little contrast. A text over an image, a gradient or a clipped background is NOT judged (its backdrop is not a
39
+ * color: guessing would be wrong as often as right); one with no color is not either. Opacity IS judged: a faded block is as hard to read as it looks.
40
+ * `resolve` turns any CSS color (`oklch(…)`, `rgb(…)`, a name) into sRGB: in a browser, a canvas does it.
41
+ */
42
+ export declare function measureContrast(root: Document, resolve: (css: string) => Rgba | null): ContrastFinding[];
43
+ /** CSS color → sRGB, by letting a canvas paint it (it understands every notation the browser does, `oklch` included). */
44
+ export declare function canvasResolver(root: Document): (css: string) => Rgba | null;
45
+ //# sourceMappingURL=contrast.d.ts.map
@@ -0,0 +1,122 @@
1
+ // Is the text readable? The WCAG 2 rule, and it is arithmetic: the ratio of the relative luminances of the text color and of what is behind
2
+ // it must be 4.5:1 (3:1 for large text). The arithmetic is here and is pure; reading the colors off the page is `measureContrast`.
3
+ /** sRGB channel (0–255) to linear light. */
4
+ const linear = (channel) => {
5
+ const c = channel / 255;
6
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
7
+ };
8
+ /** Relative luminance, 0 (black) to 1 (white). */
9
+ export const luminance = ({ r, g, b }) => 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
10
+ /** The contrast ratio of two colors, 1 (none) to 21 (black on white). */
11
+ export function contrastRatio(a, b) {
12
+ const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x);
13
+ return (light + 0.05) / (dark + 0.05);
14
+ }
15
+ /** A color with some transparency, laid over another one that may have some too. */
16
+ export function over(top, under) {
17
+ const a = top.a + under.a * (1 - top.a);
18
+ if (a === 0)
19
+ return { r: 0, g: 0, b: 0, a: 0 };
20
+ const mix = (t, u) => (t * top.a + u * under.a * (1 - top.a)) / a;
21
+ return { r: mix(top.r, under.r), g: mix(top.g, under.g), b: mix(top.b, under.b), a };
22
+ }
23
+ const WHITE = { r: 255, g: 255, b: 255, a: 1 };
24
+ /**
25
+ * What a pixel looks like in the end, where `top` is painted (the text's color, or nothing at all to get the backdrop) inside these layers,
26
+ * the nearest first: each layer paints its background under what its children painted, and is then shown with its opacity (so a faded
27
+ * block fades its text and its background together); the page is white under all. Opacity counts: text in a block at 50% is half as dark.
28
+ */
29
+ export function flatten(top, layers) {
30
+ let pixel = top;
31
+ for (const { background, opacity } of layers) {
32
+ pixel = over(pixel, background);
33
+ pixel = { ...pixel, a: pixel.a * opacity };
34
+ if (pixel.a >= 1)
35
+ break;
36
+ }
37
+ const { r, g, b } = over(pixel, WHITE);
38
+ return { r, g, b };
39
+ }
40
+ /** WCAG 2.1 AA (1.4.3): large text (24px, or 18.66px bold) needs 3:1; the rest needs 4.5:1. */
41
+ export const requiredRatio = (fontSizePx, fontWeight) => fontSizePx >= 24 || (fontSizePx >= 18.66 && fontWeight >= 700) ? 3 : 4.5;
42
+ const MAX_ELEMENTS = 400;
43
+ /**
44
+ * Looks at the text that is on the page now, with the colors the browser computed (the theme, the breakpoint and the dark mode are all in
45
+ * them), and says which has too little contrast. A text over an image, a gradient or a clipped background is NOT judged (its backdrop is not a
46
+ * color: guessing would be wrong as often as right); one with no color is not either. Opacity IS judged: a faded block is as hard to read as it looks.
47
+ * `resolve` turns any CSS color (`oklch(…)`, `rgb(…)`, a name) into sRGB: in a browser, a canvas does it.
48
+ */
49
+ export function measureContrast(root, resolve) {
50
+ const view = root.defaultView;
51
+ if (!view || !root.body)
52
+ return [];
53
+ const worst = new Map();
54
+ const elements = Array.from(root.body.querySelectorAll('*')).slice(0, MAX_ELEMENTS * 8);
55
+ let looked = 0;
56
+ for (const el of elements) {
57
+ if (looked >= MAX_ELEMENTS)
58
+ break;
59
+ const words = Array.from(el.childNodes).filter(n => n.nodeType === 3).map(n => n.textContent ?? '').join(' ').replace(/\s+/g, ' ').trim();
60
+ if (!words)
61
+ continue;
62
+ const id = el.closest('[data-wr-id]')?.getAttribute('data-wr-id');
63
+ if (!id)
64
+ continue;
65
+ const style = view.getComputedStyle(el);
66
+ if (style.display === 'none' || style.visibility === 'hidden' || el.getClientRects().length === 0)
67
+ continue;
68
+ looked += 1;
69
+ const layers = [];
70
+ let judged = true;
71
+ for (let node = el; node && judged; node = node.parentElement) {
72
+ const s = view.getComputedStyle(node);
73
+ // An image or a gradient behind the text, or text that is a clipped background: the backdrop is not a color, nothing is guessed
74
+ if (s.backgroundImage !== 'none' || (s.backgroundClip ?? '').includes('text')) {
75
+ judged = false;
76
+ break;
77
+ }
78
+ const background = resolve(s.backgroundColor) ?? { r: 0, g: 0, b: 0, a: 0 };
79
+ const opacity = Number(s.opacity);
80
+ layers.push({ background, opacity: Number.isFinite(opacity) ? opacity : 1 });
81
+ if (background.a >= 1 && opacity >= 1)
82
+ break; // opaque: what is behind it cannot be seen
83
+ }
84
+ if (!judged)
85
+ continue;
86
+ const text = resolve(style.color);
87
+ if (!text || text.a === 0)
88
+ continue;
89
+ const behind = flatten({ r: 0, g: 0, b: 0, a: 0 }, layers);
90
+ const ratio = contrastRatio(flatten(text, layers), behind);
91
+ const required = requiredRatio(parseFloat(style.fontSize), Number(style.fontWeight) || 400);
92
+ if (ratio >= required)
93
+ continue;
94
+ const finding = { elementId: id, ratio: Math.round(ratio * 100) / 100, required, text: words.slice(0, 40) };
95
+ const known = worst.get(id);
96
+ if (!known || finding.ratio < known.ratio)
97
+ worst.set(id, finding);
98
+ }
99
+ return [...worst.values()];
100
+ }
101
+ /** CSS color → sRGB, by letting a canvas paint it (it understands every notation the browser does, `oklch` included). */
102
+ export function canvasResolver(root) {
103
+ const canvas = root.createElement('canvas');
104
+ canvas.width = canvas.height = 1;
105
+ const context = canvas.getContext('2d', { willReadFrequently: true });
106
+ const cache = new Map();
107
+ return (css) => {
108
+ if (!context)
109
+ return null;
110
+ if (cache.has(css))
111
+ return cache.get(css) ?? null;
112
+ context.clearRect(0, 0, 1, 1);
113
+ context.fillStyle = '#000';
114
+ context.fillStyle = css;
115
+ context.fillRect(0, 0, 1, 1);
116
+ const [r, g, b, a] = context.getImageData(0, 0, 1, 1).data;
117
+ const value = { r, g, b, a: a / 255 };
118
+ cache.set(css, value);
119
+ return value;
120
+ };
121
+ }
122
+ //# sourceMappingURL=contrast.js.map
package/dist/drop.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { WrDropTarget } from './messages';
2
+ /**
3
+ * Where `dragged` (an element of the page, or null for a new block that is not there yet) would land if it were let go at (x, y) of the page; null if the pointer is over nothing the editor
4
+ * knows, or is over the element itself (or something inside it).
5
+ */
6
+ export declare function wrDropTargetAt(dragged: Element | null, x: number, y: number, doc?: Document): WrDropTarget | null;
7
+ //# sourceMappingURL=drop.d.ts.map