open-wysiwyg-editor 1.0.0 → 1.0.1

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 (37) hide show
  1. package/README.md +266 -10
  2. package/dist/{chunk-F3DLZFDY.js → chunk-4SVBPZWJ.js} +105 -5
  3. package/dist/chunk-4SVBPZWJ.js.map +1 -0
  4. package/dist/{chunk-2WBWKTLE.js → chunk-77S4XJMD.js} +3 -3
  5. package/dist/{chunk-2WBWKTLE.js.map → chunk-77S4XJMD.js.map} +1 -1
  6. package/dist/{chunk-MARI3ZF6.cjs → chunk-HNGUZYL2.cjs} +2 -2
  7. package/dist/{chunk-MARI3ZF6.cjs.map → chunk-HNGUZYL2.cjs.map} +1 -1
  8. package/dist/{chunk-JZHAUAUJ.cjs → chunk-KO7YL3DO.cjs} +214 -112
  9. package/dist/chunk-KO7YL3DO.cjs.map +1 -0
  10. package/dist/{chunk-2OSX7R22.js → chunk-N662ZMGU.js} +2 -2
  11. package/dist/{chunk-2OSX7R22.js.map → chunk-N662ZMGU.js.map} +1 -1
  12. package/dist/{chunk-HKWFKMAC.cjs → chunk-YA2CQM23.cjs} +5 -5
  13. package/dist/{chunk-HKWFKMAC.cjs.map → chunk-YA2CQM23.cjs.map} +1 -1
  14. package/dist/content.css +47 -33
  15. package/dist/content.min.css +1 -1
  16. package/dist/{editor-BKYDbfo1.d.ts → editor-Cx1TcWt7.d.ts} +28 -4
  17. package/dist/{editor-CY40AOtO.d.cts → editor-QMoMMBL6.d.cts} +28 -4
  18. package/dist/element.cjs +22 -8
  19. package/dist/element.cjs.map +1 -1
  20. package/dist/element.d.cts +5 -3
  21. package/dist/element.d.ts +5 -3
  22. package/dist/element.js +19 -5
  23. package/dist/element.js.map +1 -1
  24. package/dist/headless.cjs +87 -87
  25. package/dist/headless.d.cts +1 -1
  26. package/dist/headless.d.ts +1 -1
  27. package/dist/headless.js +2 -2
  28. package/dist/index.cjs +108 -100
  29. package/dist/index.d.cts +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +3 -3
  32. package/dist/open-wysiwyg-editor.global.js +16 -16
  33. package/dist/style.css +88 -68
  34. package/dist/style.min.css +1 -1
  35. package/package.json +1 -1
  36. package/dist/chunk-F3DLZFDY.js.map +0 -1
  37. package/dist/chunk-JZHAUAUJ.cjs.map +0 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  An accessible rich text editor that works on **any website**: plain HTML/JS, React, Next.js, Preact, Vue, Nuxt, Angular, Svelte, Solid, Astro, WordPress and more. It's RTL-first, it runs under a strict Content Security Policy, and it ships its own TypeScript types. MIT licensed, with no license key, no telemetry and no cloud service.
12
12
 
13
- Built on [ProseMirror](https://prosemirror.net). Content is stored as HTML or JSON, and node and mark names match Tiptap's.
13
+ Built on [ProseMirror](https://prosemirror.net). Content is stored as HTML or JSON.
14
14
 
15
15
  ## Features
16
16
 
@@ -23,7 +23,7 @@ Built on [ProseMirror](https://prosemirror.net). Content is stored as HTML or JS
23
23
  | **Security** | DOMPurify on every input · URL allow-list · Trusted Types · No `unsafe-inline` needed |
24
24
  | **Languages** | English and Arabic built in (add your own) · Mirrored UI in RTL · `dir="auto"` on every block |
25
25
  | **Everywhere** | `<owe-editor>` web component for plain HTML and forms · Packages for React, Next.js, Preact, Vue, Nuxt, Angular, Svelte, Solid and Astro |
26
- | **Your way** | Full UI by default · Headless mode for your own UI · Custom toolbar buttons · Extensions API · Theme with CSS variables |
26
+ | **Your way** | Full UI by default · Headless mode for your own UI · Custom toolbar buttons · Extensions API · Brand it with one color or CSS variables |
27
27
 
28
28
  Every release is tested end to end in Chromium, Firefox and WebKit, under a strict CSP with Trusted Types and with axe-core accessibility checks.
29
29
 
@@ -432,11 +432,13 @@ Still sanitize on the server. To display saved HTML on a page that has no editor
432
432
  | `dir` | Base direction of the content (`ltr`, `rtl`, `auto`) |
433
433
  | `content-lang` | `lang` of the content (spellcheck and screen readers) |
434
434
  | `label` | Accessible name. Or point a `<label for>` at the element's `id` |
435
+ | `theme` | `auto` (follows the system), `light` or `dark` |
436
+ | `brand` | Brand color (any CSS color): buttons, links, focus ring and selection follow it |
435
437
  | `value` | Initial content as HTML (read once, when the editor is created) |
436
438
  | `language` | UI language, e.g. `ar` (read once). Defaults to `<html lang>` |
437
439
  | `toolbar` | Toolbar items separated by spaces (`"bold italic \| link"`), or `none` (read once) |
438
440
 
439
- `placeholder`, `readonly`, `disabled`, `dir`, `content-lang` and `label` follow later changes. `value`, `language` and `toolbar` are only read when the editor starts.
441
+ `placeholder`, `readonly`, `disabled`, `dir`, `content-lang`, `label`, `theme` and `brand` follow later changes. `value`, `language` and `toolbar` are only read when the editor starts.
440
442
 
441
443
  | Property | Description |
442
444
  | --- | --- |
@@ -489,6 +491,8 @@ createEditor({
489
491
  items: { /* custom buttons, see below */ },
490
492
  statusbar: { elementPath: true, wordCount: true, characterCount: false }, // or false
491
493
  theme: "auto", // "light" | "dark"
494
+ brand: "#e11d48", // one color for buttons, links, focus and selection (see Styling)
495
+ tokens: { radius: "8px" }, // any design token, without the --owe- prefix (see Styling)
492
496
  stickyToolbar: true,
493
497
  shortcuts: { toolbar: "Alt-F10", help: "Alt-0", find: "Mod-f", link: "Mod-k" },
494
498
  },
@@ -520,7 +524,7 @@ editor.announce("Saved"); // send a message to screen readers
520
524
  editor.destroy();
521
525
  ```
522
526
 
523
- The main entry also exports `getUI(editor)`, which returns `focusToolbar()`, `openDialog(kind)`, `openFind()`, `toggleSource()`, `isSourceMode()` and `update()`.
527
+ The main entry also exports `getUI(editor)`, which returns `focusToolbar()`, `openDialog(kind)`, `openFind()`, `toggleSource()`, `isSourceMode()`, `update()` and `setTheme({ theme, brand, tokens })` (see [Styling](#styling)).
524
528
 
525
529
  ### Commands
526
530
 
@@ -708,16 +712,268 @@ Untranslated labels fall back to English, and plurals use `Intl.PluralRules`. Th
708
712
 
709
713
  Each framework package ships the same files under its own name, for example `@open-wysiwyg-editor/vue/style.css` and `@open-wysiwyg-editor/vue/content.css`, because package managers like pnpm don't expose the core package to your app.
710
714
 
711
- All rules are in `@layer owe`, so any of your own CSS outside a layer overrides them. To theme the editor, set the design tokens:
715
+ All rules are in `@layer owe`, so any of your own CSS outside a layer overrides them.
716
+
717
+ ### One color
718
+
719
+ Pick your brand color and the whole editor follows it, in light and dark: the pressed toolbar buttons, the dialog buttons, links, the focus ring, the caret, the text selection and the gradient edge of the card.
720
+
721
+ ```ts
722
+ createEditor({ element, ui: { brand: "#e11d48" } });
723
+ ```
724
+
725
+ ```html
726
+ <owe-editor brand="#e11d48" theme="dark"></owe-editor>
727
+ ```
728
+
729
+ ```css
730
+ .owe { --owe-brand: #e11d48; }
731
+ ```
732
+
733
+ All three do the same thing. `brand` and the attribute take any CSS color. They also pick black or white for the text on brand-colored buttons (`--owe-on-brand`), by contrast, when the color is a hex, `rgb()`, `hsl()` or basic named color. With plain CSS, set `--owe-on-brand` yourself if white doesn't read well on your color. `--owe-brand` can be set on `.owe`, on a wrapper or on `:root`.
734
+
735
+ Everything else is derived from the brand with `color-mix()`, so you only override what you want to change:
736
+
737
+ | Token | Derived as |
738
+ | --- | --- |
739
+ | `--owe-primary` | The brand. Pressed toolbar buttons, primary dialog buttons, the current find match |
740
+ | `--owe-primary-hover` | `--owe-primary` mixed 15% toward black |
741
+ | `--owe-accent` | Text and link color. The brand mixed 30% toward black in light, 40% toward white in dark |
742
+ | `--owe-accent-soft` | The brand at 18% over `--owe-bg` |
743
+ | `--owe-focus`, `--owe-caret` | `--owe-accent` |
744
+ | `--owe-selection` | The brand at 22% opacity in light, 45% in dark |
745
+ | `--owe-on-accent` | `--owe-on-brand` |
746
+ | `--owe-gradient` | `linear-gradient(90deg, var(--owe-brand), var(--owe-brand-2))` |
747
+
748
+ Setting one of these tokens yourself always wins over the derived value. `ui.brand` also sets `--owe-brand-2` to a lighter tint of the brand, so the card edge fades from your color. With plain CSS the second stop stays `#00b8ff`: set `--owe-brand-2` to the same color as `--owe-brand` for a solid edge.
749
+
750
+ The editor can't pick your colors for you. `--owe-accent` keeps 4.5:1 on white for typical brands, but a very pale brand (yellow, light cyan) or a very dark one in dark mode may need an explicit `--owe-accent`. Pressed buttons need 3:1 against the toolbar (WCAG 1.4.11): for a very dark brand in dark mode, set `--owe-btn-active-bg: var(--owe-accent)` and `--owe-btn-active-color` to a dark color.
751
+
752
+ ### Design tokens
753
+
754
+ Set any token on `.owe` in your own CSS, or pass it as `ui.tokens` without the `--owe-` prefix (`tokens: { bg: "#0b0d10", radius: "8px" }`). Tokens set from JavaScript are applied through the CSSOM, so they work under a strict CSP. Unknown names are ignored, and so are values that contain `;`, `{`, `}`, `<` or `\`, or that call `url()`, `image()`, `image-set()` or `src()`. In TypeScript the names are the `ThemeToken` type.
755
+
756
+ | Token | Light | Dark | Styles |
757
+ | --- | --- | --- | --- |
758
+ | `--owe-brand` | `#0066ff` | same | The one brand color (see above) |
759
+ | `--owe-brand-2` | `#00b8ff` | same | Second color of the gradient edge |
760
+ | `--owe-on-brand` | `#ffffff` | same | Text and icons on brand fills |
761
+ | `--owe-primary` | brand | brand | Solid fills: pressed buttons, primary buttons |
762
+ | `--owe-primary-hover` | brand, 15% darker | same | Hover on those fills |
763
+ | `--owe-accent` | brand, 30% darker (`#003c9e`) | brand, 40% lighter (`#75a9ff`) | Links, checked menu items, the current path item |
764
+ | `--owe-accent-soft` | `#d1e3ff` | `#132744` | A brand tint for your own UI. The editor itself doesn't use it |
765
+ | `--owe-focus` | accent | accent | Focus ring, selected image and table outlines |
766
+ | `--owe-caret` | accent | accent | Text caret |
767
+ | `--owe-selection` | brand, 22% opacity | brand, 45% opacity | Selected text and table cells |
768
+ | `--owe-gradient` | brand to brand-2 | same | Bottom edge of the card |
769
+ | `--owe-bg` | `#ffffff` | `#17191b` | Card and editing area |
770
+ | `--owe-chrome` | `#fafbfc` | `#131517` | Toolbar, find bar and status bar |
771
+ | `--owe-surface` | `#f4f6f8` | `#1e2124` | Menus and dialogs |
772
+ | `--owe-surface-2` | `#f1f3f5` | `#25282b` | Hover backgrounds, keys in the shortcut list |
773
+ | `--owe-field-bg` | `#ffffff` | `#101214` | Inputs and selects |
774
+ | `--owe-code-bg` | `#f4f6f8` | `#0f1112` | Code, the source view |
775
+ | `--owe-text` | `#101112` | `#ffffff` | Headings and UI text |
776
+ | `--owe-body` | `#1b1f24` | `#dcdedf` | Paragraph text |
777
+ | `--owe-text-2` | `#3d434a` | `#c3c7cb` | Quotes |
778
+ | `--owe-muted` | `#54595f` | `#a1a7ad` | Placeholder, hints, shortcuts, status bar |
779
+ | `--owe-icon` | `#3a3f45` | `#d2d4d5` | Toolbar icons |
780
+ | `--owe-border` | `#8a929a` | `#6b7279` | Input and swatch borders (3:1 on the card) |
781
+ | `--owe-border-subtle` | `#e3e6ea` | `#25282b` | Card edge and dividers |
782
+ | `--owe-popup-border` | `#d5d9de` | `#33383d` | Menu and dialog edge |
783
+ | `--owe-danger` | `#b3261e` | `#f2b8b5` | Errors and destructive buttons |
784
+ | `--owe-warning` | `#8a5300` | `#f6c66b` | Find-match outline |
785
+ | `--owe-success` | `#146c2e` | `#8bd6a0` | For your own success messages. The editor itself doesn't use it |
786
+ | `--owe-mark` | `#fff2a8` | `#6b4e00` | `<mark>` and find matches |
787
+ | `--owe-shadow` | soft grey shadow | black shadow | Menus and dialogs |
788
+ | `--owe-backdrop` | `rgb(16 17 18 / 0.45)` | `rgb(4 5 16 / 0.65)` | Behind dialogs |
789
+ | `--owe-font` | `system-ui, "Segoe UI", "Noto Sans Arabic", …` | same | UI and content typeface. No web fonts are bundled |
790
+ | `--owe-font-mono` | `ui-monospace, Menlo, Consolas, …` | same | Code and the status bar |
791
+ | `--owe-font-size`, `--owe-line-height` | `1rem`, `1.65` | same | Content text |
792
+ | `--owe-radius` | `24px` | same | The card |
793
+ | `--owe-radius-sm`, `-md`, `-lg` | `10px`, `12px`, `16px` | same | Controls, menus, dialogs |
794
+ | `--owe-btn-size` | `40px` | same | Toolbar button size |
795
+ | `--owe-target` | `32px` | same | Minimum height of menu items, the status bar and small controls |
796
+ | `--owe-pad-x`, `--owe-pad-y` | `clamp(16px, 4vw, 32px)`, `28px` | same | Padding of the editing area |
797
+ | `--owe-z-popover` | `1000` | same | `z-index` of menus and tooltips |
798
+
799
+ In dark mode the editor uses the dark column when the system prefers dark, or when `theme` is `dark`. `theme: "light"` turns that off.
800
+
801
+ ### Button tokens
802
+
803
+ These restyle the buttons, the toolbar and the card without touching any selector. They have no default value declared, so you can set them on `.owe` or on any element around it, such as `:root` or your dashboard wrapper. The default in the table is what the editor uses when you don't set them.
804
+
805
+ | Token | Default | Styles |
806
+ | --- | --- | --- |
807
+ | `--owe-btn-color` | `--owe-icon` | Toolbar button icon and text |
808
+ | `--owe-btn-bg` | `transparent` | Toolbar button background |
809
+ | `--owe-btn-hover-color` | `--owe-text` | Button and path item text on hover |
810
+ | `--owe-btn-hover-bg` | `--owe-surface-2` | Hover background of buttons, menu items and path items |
811
+ | `--owe-btn-active-color` | `--owe-on-accent` | Text and icon of a pressed or toggled button |
812
+ | `--owe-btn-active-bg` | `--owe-primary` | Background of a pressed or toggled button |
813
+ | `--owe-btn-active-hover-bg` | `--owe-btn-active-bg`, 15% darker | Hover on a pressed button |
814
+ | `--owe-btn-radius` | `--owe-radius-sm` | Corners of buttons, menu items and path items |
815
+ | `--owe-btn-primary-bg` | `--owe-primary` | Primary button in dialogs (Insert, Apply) |
816
+ | `--owe-btn-primary-color` | `--owe-on-accent` | Text of the primary button |
817
+ | `--owe-btn-primary-hover-bg` | `--owe-primary-hover` | Primary button on hover |
818
+ | `--owe-toolbar-bg` | `--owe-chrome` | Toolbar, find bar and status bar background |
819
+ | `--owe-toolbar-border` | `--owe-border-subtle` | Lines between toolbar, content and status bar |
820
+ | `--owe-editor-border` | `--owe-border-subtle` | Border of the card |
821
+ | `--owe-editor-radius` | `--owe-radius` | Corners of the card, toolbar and status bar |
822
+
823
+ A dark toolbar over a light editing area, with smaller corners:
824
+
825
+ ```css
826
+ .owe {
827
+ --owe-toolbar-bg: #0f172a;
828
+ --owe-toolbar-border: #0f172a;
829
+ --owe-btn-color: #e2e8f0;
830
+ --owe-btn-hover-bg: #1e293b;
831
+ --owe-btn-hover-color: #ffffff;
832
+ --owe-btn-radius: 6px;
833
+ --owe-editor-radius: 8px;
834
+ }
835
+ ```
836
+
837
+ Windows High Contrast (`forced-colors`) and `prefers-contrast: more` still take over where they must: pressed buttons use the system highlight colors whatever you set.
838
+
839
+ ### Match your dashboard
840
+
841
+ Point the tokens at the variables your design system already defines. Put the CSS after the editor's stylesheet, or anywhere, because your unlayered CSS beats `@layer owe`. Because the tokens read your variables when they are used, the colors follow your light and dark themes without any script.
842
+
843
+ Tell the editor when your dashboard is dark as well: set `theme` to `dark` (`<owe-editor theme="dark">`, `ui.theme` or `setTheme()`). That switches the parts that are derived from the brand for dark backgrounds (links, selection), the highlight color and `color-scheme`, so scrollbars and native controls match. With `theme` left on `auto`, the editor follows the system setting.
844
+
845
+ **shadcn/ui** (Tailwind v4 variables):
846
+
847
+ ```css
848
+ .owe {
849
+ --owe-brand: var(--primary);
850
+ --owe-brand-2: var(--primary);
851
+ --owe-on-brand: var(--primary-foreground);
852
+ --owe-bg: var(--card);
853
+ --owe-text: var(--card-foreground);
854
+ --owe-body: var(--card-foreground);
855
+ --owe-chrome: var(--muted);
856
+ --owe-surface: var(--popover);
857
+ --owe-surface-2: var(--accent);
858
+ --owe-field-bg: var(--background);
859
+ --owe-muted: var(--muted-foreground);
860
+ --owe-icon: var(--foreground);
861
+ --owe-border-subtle: var(--border);
862
+ --owe-popup-border: var(--border);
863
+ /* --owe-border draws input outlines, which need 3:1; shadcn's --border is much lighter */
864
+ --owe-border: color-mix(in oklab, var(--foreground) 45%, var(--card));
865
+ --owe-radius: var(--radius);
866
+ --owe-radius-sm: calc(var(--radius) - 2px);
867
+ --owe-font: inherit;
868
+ }
869
+ ```
870
+
871
+ **Bootstrap 5.3** (`data-bs-theme` switches light and dark):
872
+
873
+ ```css
874
+ .owe {
875
+ --owe-brand: var(--bs-primary);
876
+ --owe-brand-2: var(--bs-primary);
877
+ --owe-accent: var(--bs-link-color);
878
+ --owe-bg: var(--bs-body-bg);
879
+ --owe-chrome: var(--bs-tertiary-bg);
880
+ --owe-surface: var(--bs-body-bg);
881
+ --owe-surface-2: var(--bs-secondary-bg);
882
+ --owe-field-bg: var(--bs-body-bg);
883
+ --owe-text: var(--bs-emphasis-color);
884
+ --owe-body: var(--bs-body-color);
885
+ --owe-muted: var(--bs-secondary-color);
886
+ --owe-icon: var(--bs-body-color);
887
+ --owe-border-subtle: var(--bs-border-color);
888
+ --owe-popup-border: var(--bs-border-color);
889
+ --owe-radius: var(--bs-border-radius-xl);
890
+ --owe-radius-sm: var(--bs-border-radius);
891
+ --owe-font: inherit;
892
+ }
893
+ ```
894
+
895
+ **Material UI** (v6 and later, with `createTheme({ cssVariables: true, colorSchemes: { dark: true } })`):
712
896
 
713
897
  ```css
714
898
  .owe {
715
- --owe-accent: #0b57d0;
716
- --owe-font: "IBM Plex Sans Arabic", system-ui, sans-serif;
717
- --owe-radius: 10px;
899
+ --owe-brand: var(--mui-palette-primary-main);
900
+ --owe-brand-2: var(--mui-palette-primary-main);
901
+ --owe-on-brand: var(--mui-palette-primary-contrastText);
902
+ --owe-accent: var(--mui-palette-primary-main);
903
+ --owe-bg: var(--mui-palette-background-paper);
904
+ --owe-chrome: var(--mui-palette-background-default);
905
+ --owe-surface: var(--mui-palette-background-paper);
906
+ --owe-field-bg: var(--mui-palette-background-paper);
907
+ --owe-text: var(--mui-palette-text-primary);
908
+ --owe-body: var(--mui-palette-text-primary);
909
+ --owe-muted: var(--mui-palette-text-secondary);
910
+ --owe-icon: var(--mui-palette-text-secondary);
911
+ --owe-border: var(--mui-palette-text-secondary);
912
+ --owe-border-subtle: var(--mui-palette-divider);
913
+ --owe-popup-border: var(--mui-palette-divider);
914
+ --owe-btn-hover-bg: var(--mui-palette-action-hover);
915
+ --owe-radius: calc(var(--mui-shape-borderRadius) * 3);
916
+ --owe-radius-sm: var(--mui-shape-borderRadius);
917
+ --owe-font: inherit;
718
918
  }
719
919
  ```
720
920
 
921
+ **A plain dark dashboard** with its own palette:
922
+
923
+ ```css
924
+ .owe {
925
+ --owe-brand: #38bdf8;
926
+ --owe-on-brand: #0b1120; /* white is only 2:1 on this blue */
927
+ --owe-bg: #0f172a;
928
+ --owe-chrome: #0b1120;
929
+ --owe-surface: #1e293b;
930
+ --owe-surface-2: #263449;
931
+ --owe-field-bg: #0b1120;
932
+ --owe-text: #f8fafc;
933
+ --owe-body: #e2e8f0;
934
+ --owe-muted: #94a3b8;
935
+ --owe-icon: #cbd5e1;
936
+ --owe-border: #64748b;
937
+ --owe-border-subtle: #1e293b;
938
+ --owe-popup-border: #334155;
939
+ --owe-radius: 12px;
940
+ }
941
+ ```
942
+
943
+ Then switch the editor to its dark theme with `<owe-editor theme="dark">`, or the same from JavaScript, without any CSS. `brand` picks the dark text on this blue by itself:
944
+
945
+ ```ts
946
+ createEditor({
947
+ element,
948
+ ui: {
949
+ theme: "dark",
950
+ brand: "#38bdf8",
951
+ tokens: { bg: "#0f172a", chrome: "#0b1120", surface: "#1e293b", radius: "12px" },
952
+ },
953
+ });
954
+ ```
955
+
956
+ ### Changing the theme later
957
+
958
+ `<owe-editor>` follows changes to its `theme` and `brand` attributes. For an editor you created with `createEditor()`, call `setTheme()` on its UI. It replaces the whole theme, so pass everything you want to keep:
959
+
960
+ ```ts
961
+ import { getUI } from "open-wysiwyg-editor";
962
+
963
+ getUI(editor)?.setTheme({ theme: "dark", brand: "#e11d48", tokens: { radius: "8px" } });
964
+ ```
965
+
966
+ The framework components read `ui` once, when the editor starts, so call `setTheme()` when your own state changes:
967
+
968
+ ```tsx
969
+ const { ref, editor } = useEditor({ ui: { brand } });
970
+ useEffect(() => {
971
+ if (editor) getUI(editor)?.setTheme({ brand });
972
+ }, [editor, brand]);
973
+ ```
974
+
975
+ `applyTheme(element, { theme, brand, tokens })` is the function behind `setTheme()`. Use it on any element, for example the `.owe-content-root` that shows saved HTML. CSS variables also change at runtime: set `--owe-brand` on `.owe`, or on a wrapper, whenever you like.
976
+
721
977
  ## Extensions
722
978
 
723
979
  Everything is built from extensions. You can write your own the same way:
@@ -815,14 +1071,14 @@ npm install
815
1071
  npm run build # builds every package
816
1072
  npm run check # lint + build + typecheck + unit tests
817
1073
  npm run e2e # end-to-end + axe tests in Chromium, Firefox and WebKit (Playwright)
818
- npm run dev # serve the demo site locally
1074
+ npm run dev # serve the website locally (http://localhost:3000/open-wysiwyg-editor/)
819
1075
  ```
820
1076
 
821
1077
  Layout:
822
1078
 
823
1079
  ```
824
1080
  README.md CHANGELOG.md CONTRIBUTING.md SECURITY.md LICENSE
825
- examples/ the documentation website + live demo (deployed to GitHub Pages)
1081
+ examples/ the documentation website + live demo: one Next.js page in English and Arabic (deployed to GitHub Pages)
826
1082
  test/e2e/ Playwright end-to-end + axe tests
827
1083
  scripts/ serve-e2e, size-check, license-check, copy-styles, publish, render-banner
828
1084
  packages/<name>/ core, react, next, preact, vue, nuxt, angular, svelte, solid, astro
@@ -1,4 +1,4 @@
1
- import { isMac, svg, h, createEditor, StarterKit, normalizeColor, uid, getSearchState, markRange, docText, countWords, countCharacters, contrastRatio, sanitizeUrl } from './chunk-2OSX7R22.js';
1
+ import { isMac, svg, h, createEditor, StarterKit, normalizeColor, contrastRatio, uid, getSearchState, markRange, docText, countWords, countCharacters, sanitizeUrl } from './chunk-N662ZMGU.js';
2
2
  import { NodeSelection } from 'prosemirror-state';
3
3
 
4
4
  /*! open-wysiwyg-editor | MIT License | https://github.com/alhassan73/open-wysiwyg-editor */
@@ -870,6 +870,105 @@ function customItem(spec) {
870
870
  };
871
871
  };
872
872
  }
873
+
874
+ // src/ui/theme.ts
875
+ var THEME_TOKENS = [
876
+ // brand
877
+ "brand",
878
+ "brand-2",
879
+ "on-brand",
880
+ "primary",
881
+ "primary-hover",
882
+ "accent",
883
+ "on-accent",
884
+ "accent-soft",
885
+ "focus",
886
+ "caret",
887
+ "selection",
888
+ "gradient",
889
+ // surfaces, text and borders
890
+ "bg",
891
+ "chrome",
892
+ "surface",
893
+ "surface-2",
894
+ "field-bg",
895
+ "code-bg",
896
+ "text",
897
+ "body",
898
+ "text-2",
899
+ "muted",
900
+ "icon",
901
+ "border",
902
+ "border-subtle",
903
+ "popup-border",
904
+ "danger",
905
+ "warning",
906
+ "success",
907
+ "mark",
908
+ "shadow",
909
+ "backdrop",
910
+ // type, shape and spacing
911
+ "font",
912
+ "font-mono",
913
+ "font-size",
914
+ "line-height",
915
+ "radius",
916
+ "radius-sm",
917
+ "radius-md",
918
+ "radius-lg",
919
+ "space",
920
+ "target",
921
+ "btn-size",
922
+ "pad-x",
923
+ "pad-y",
924
+ "z-popover",
925
+ // buttons and chrome
926
+ "btn-color",
927
+ "btn-bg",
928
+ "btn-radius",
929
+ "btn-hover-color",
930
+ "btn-hover-bg",
931
+ "btn-active-color",
932
+ "btn-active-bg",
933
+ "btn-active-hover-bg",
934
+ "btn-primary-bg",
935
+ "btn-primary-color",
936
+ "btn-primary-hover-bg",
937
+ "toolbar-bg",
938
+ "toolbar-border",
939
+ "editor-border",
940
+ "editor-radius"
941
+ ];
942
+ var PREFIX = "--owe-";
943
+ var KNOWN = new Set(THEME_TOKENS);
944
+ var ON_DARK = "#101112";
945
+ var UNSAFE = /[;{}<\\]|\b(?:url|image-set|image|src|expression)\s*\(/i;
946
+ var applied = /* @__PURE__ */ new WeakMap();
947
+ var safe = (value) => typeof value === "string" && !!value.trim() && !UNSAFE.test(value);
948
+ function onBrand(color) {
949
+ const white = contrastRatio(color, "#ffffff");
950
+ const dark = contrastRatio(color, ON_DARK);
951
+ return white === null || dark === null ? null : dark > white ? ON_DARK : "#ffffff";
952
+ }
953
+ function applyTheme(root, { theme, brand, tokens } = {}) {
954
+ if (theme === "light" || theme === "dark") root.dataset.theme = theme;
955
+ else delete root.dataset.theme;
956
+ const next = /* @__PURE__ */ new Map();
957
+ if (safe(brand)) {
958
+ next.set("brand", brand.trim());
959
+ next.set("brand-2", "color-mix(in oklab, var(--owe-brand) 65%, white)");
960
+ const on = onBrand(brand);
961
+ if (on) next.set("on-brand", on);
962
+ }
963
+ for (const [key, value] of Object.entries(tokens ?? {})) {
964
+ if (KNOWN.has(key) && safe(value)) next.set(key, value.trim());
965
+ }
966
+ for (const key of applied.get(root) ?? []) {
967
+ if (!next.has(key)) root.style.removeProperty(PREFIX + key);
968
+ }
969
+ for (const [key, value] of next) root.style.setProperty(PREFIX + key, value);
970
+ applied.set(root, new Set(next.keys()));
971
+ }
873
972
  var policyOf = (editor) => editor.schema.cached.urlPolicy ?? {};
874
973
  function normalizeUrlInput(raw) {
875
974
  const value = raw.trim();
@@ -1559,7 +1658,7 @@ function attachUI(editor, options = {}) {
1559
1658
  link: "Mod-k",
1560
1659
  ...options.shortcuts
1561
1660
  };
1562
- if (options.theme && options.theme !== "auto") root.dataset.theme = options.theme;
1661
+ applyTheme(root, options);
1563
1662
  const findBar = "setSearch" in editor.commands ? createFindBar(editor, tooltip) : null;
1564
1663
  function openDialog2(kind, returnTo) {
1565
1664
  const ctx = {
@@ -1670,6 +1769,7 @@ function attachUI(editor, options = {}) {
1670
1769
  toggleSource,
1671
1770
  isSourceMode: source.isOpen,
1672
1771
  update,
1772
+ setTheme: (theme) => applyTheme(root, theme),
1673
1773
  destroy() {
1674
1774
  cancelAnimationFrame(frame);
1675
1775
  source.destroy();
@@ -1729,6 +1829,6 @@ function syncContent(editor, content) {
1729
1829
  editor.setContent(content);
1730
1830
  }
1731
1831
 
1732
- export { BUILT_IN_ITEMS, DEFAULT_TOOLBAR, RUNTIME_OPTIONS, ariaShortcut, attachUI, createEditor2 as createEditor, customItem, formatShortcut, forwardCallbacks, getUI, pickRuntimeOptions, sameRuntimeOptions, syncContent };
1733
- //# sourceMappingURL=chunk-F3DLZFDY.js.map
1734
- //# sourceMappingURL=chunk-F3DLZFDY.js.map
1832
+ export { BUILT_IN_ITEMS, DEFAULT_TOOLBAR, RUNTIME_OPTIONS, THEME_TOKENS, applyTheme, ariaShortcut, attachUI, createEditor2 as createEditor, customItem, formatShortcut, forwardCallbacks, getUI, pickRuntimeOptions, sameRuntimeOptions, syncContent };
1833
+ //# sourceMappingURL=chunk-4SVBPZWJ.js.map
1834
+ //# sourceMappingURL=chunk-4SVBPZWJ.js.map