@inshapardaz/likhari-react 0.1.6 → 0.1.7-dev.73

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 (62) hide show
  1. package/dist/EditorRoot.d.ts +25 -1
  2. package/dist/EditorRoot.d.ts.map +1 -1
  3. package/dist/EditorRoot.js +24 -5
  4. package/dist/PortalTargetContext.d.ts +16 -0
  5. package/dist/PortalTargetContext.d.ts.map +1 -0
  6. package/dist/PortalTargetContext.js +18 -0
  7. package/dist/components/LinkDialog.d.ts.map +1 -1
  8. package/dist/components/LinkDialog.js +7 -3
  9. package/dist/components/Toolbar.d.ts +4 -1
  10. package/dist/components/Toolbar.d.ts.map +1 -1
  11. package/dist/components/Toolbar.js +246 -33
  12. package/dist/i18n/index.d.ts +4 -0
  13. package/dist/i18n/index.d.ts.map +1 -0
  14. package/dist/i18n/index.js +2 -0
  15. package/dist/i18n/strings.d.ts +158 -0
  16. package/dist/i18n/strings.d.ts.map +1 -0
  17. package/dist/i18n/strings.js +425 -0
  18. package/dist/i18n/useStrings.d.ts +10 -0
  19. package/dist/i18n/useStrings.d.ts.map +1 -0
  20. package/dist/i18n/useStrings.js +14 -0
  21. package/dist/image/ImageComponent.d.ts +23 -0
  22. package/dist/image/ImageComponent.d.ts.map +1 -0
  23. package/dist/image/ImageComponent.js +171 -0
  24. package/dist/image/ImageCropDialog.d.ts +22 -0
  25. package/dist/image/ImageCropDialog.d.ts.map +1 -0
  26. package/dist/image/ImageCropDialog.js +120 -0
  27. package/dist/image/ImageCropper.d.ts +15 -0
  28. package/dist/image/ImageCropper.d.ts.map +1 -0
  29. package/dist/image/ImageCropper.js +120 -0
  30. package/dist/image/ImageDialog.d.ts +37 -0
  31. package/dist/image/ImageDialog.d.ts.map +1 -0
  32. package/dist/image/ImageDialog.js +176 -0
  33. package/dist/image/ImageNode.d.ts +64 -0
  34. package/dist/image/ImageNode.d.ts.map +1 -0
  35. package/dist/image/ImageNode.js +133 -0
  36. package/dist/image/ImageOptionsContext.d.ts +24 -0
  37. package/dist/image/ImageOptionsContext.d.ts.map +1 -0
  38. package/dist/image/ImageOptionsContext.js +10 -0
  39. package/dist/image/cropMath.d.ts +35 -0
  40. package/dist/image/cropMath.d.ts.map +1 -0
  41. package/dist/image/cropMath.js +92 -0
  42. package/dist/image/imageEdit.d.ts +49 -0
  43. package/dist/image/imageEdit.d.ts.map +1 -0
  44. package/dist/image/imageEdit.js +132 -0
  45. package/dist/image/imageUrl.d.ts +14 -0
  46. package/dist/image/imageUrl.d.ts.map +1 -0
  47. package/dist/image/imageUrl.js +27 -0
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +2 -0
  51. package/dist/nodes.d.ts.map +1 -1
  52. package/dist/nodes.js +2 -0
  53. package/dist/theme/EditorThemeProvider.d.ts +17 -2
  54. package/dist/theme/EditorThemeProvider.d.ts.map +1 -1
  55. package/dist/theme/EditorThemeProvider.js +27 -3
  56. package/dist/theme/accentColor.d.ts +43 -0
  57. package/dist/theme/accentColor.d.ts.map +1 -0
  58. package/dist/theme/accentColor.js +84 -0
  59. package/dist/theme/editor.css +125 -0
  60. package/dist/theme/editorTheme.d.ts.map +1 -1
  61. package/dist/theme/editorTheme.js +1 -0
  62. package/package.json +9 -3
@@ -1,8 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
2
3
  import { MantineProvider } from '@mantine/core';
3
- import '@mantine/core/styles.css';
4
+ // Mantine is a peer dependency (docs/mantine-headless-theming.md §4 rule 1):
5
+ // the host imports '@mantine/core/styles.css' itself, once, in its own entry
6
+ // point, so it controls load order. This package must not import it itself.
4
7
  import { buildThemeCss } from '@inshapardaz/likhari-core';
5
8
  import { defaultMantineTheme } from './mantineTheme.js';
9
+ import { generateAccentCssVars, generateAccentShades } from './accentColor.js';
6
10
  import './editor.css';
7
11
  let cssInjected = false;
8
12
  // Scopes the --editor-* variables to the wrapper div below, which is what
@@ -21,8 +25,28 @@ function useThemeCssInjection() {
21
25
  cssInjected = true;
22
26
  }
23
27
  }
24
- export function EditorThemeProvider({ theme, colorScheme, children }) {
28
+ export function EditorThemeProvider({ theme, colorScheme, accentColor, scopeElementId, children }) {
25
29
  useThemeCssInjection();
30
+ // Mantine wants a 10-shade array; a host only supplies one color, so a
31
+ // small local helper (accentColor.ts) mixes it towards white/black for
32
+ // the rest of the ramp. If `theme` is ALSO supplied, its own `colors`
33
+ // still wins for anything it explicitly sets (shallow-merged per color
34
+ // key) — the accent override alone isn't worth a generic deep-merge here.
35
+ const resolvedTheme = useMemo(() => {
36
+ const shades = accentColor ? generateAccentShades(accentColor) : null;
37
+ const base = shades ? { ...defaultMantineTheme, colors: { ...defaultMantineTheme.colors, likhariAccent: shades } } : defaultMantineTheme;
38
+ if (!theme)
39
+ return base;
40
+ return { ...base, ...theme, colors: { ...base.colors, ...theme.colors } };
41
+ }, [accentColor, theme]);
42
+ // Inline style wins over the injected stylesheet rule (same element, same
43
+ // specificity otherwise) — only set when accentColor is actually given,
44
+ // so the default static tokens keep applying via the stylesheet the rest
45
+ // of the time.
46
+ const accentVars = accentColor ? generateAccentCssVars(accentColor, colorScheme === 'dark' ? 'dark' : 'light') : null;
47
+ const accentStyle = accentVars
48
+ ? { '--editor-accent': accentVars.accent, '--editor-accent-soft': accentVars.accentSoft }
49
+ : {};
26
50
  return (
27
51
  // display: contents keeps this wrapper out of the layout tree — CSS
28
52
  // custom properties still cascade to children through it (inheritance
@@ -30,5 +54,5 @@ export function EditorThemeProvider({ theme, colorScheme, children }) {
30
54
  // element and the host's actual parent, so EditorRoot's `height` prop
31
55
  // (e.g. '100%') resolves against the host's container as if this div
32
56
  // weren't there at all.
33
- _jsx("div", { className: "likhari-theme-scope", "data-editor-color-scheme": colorScheme, style: { display: 'contents' }, children: _jsx(MantineProvider, { theme: theme ?? defaultMantineTheme, children: children }) }));
57
+ _jsx("div", { className: "likhari-theme-scope", "data-editor-color-scheme": colorScheme, style: { display: 'contents', ...accentStyle }, children: _jsx(MantineProvider, { theme: resolvedTheme, forceColorScheme: colorScheme, defaultColorScheme: "auto", getRootElement: scopeElementId ? () => document.getElementById(scopeElementId) ?? undefined : undefined, cssVariablesSelector: scopeElementId ? `#${CSS.escape(scopeElementId)}` : undefined, children: children }) }));
34
58
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Derives Mantine's 10-shade color array and the editor's own
3
+ * `--editor-accent` / `--editor-accent-soft` pair from a single
4
+ * host-supplied accent color (EditorRootProps.accentColor). No color
5
+ * library needed — a few lines of hex parsing plus RGB lerp is enough for a
6
+ * usable (not professionally calibrated) ramp.
7
+ */
8
+ interface Rgb {
9
+ r: number;
10
+ g: number;
11
+ b: number;
12
+ }
13
+ /** Parses a `#rgb` or `#rrggbb` hex color. Returns null for anything else —
14
+ * callers fall back to the static default tokens rather than throwing on a
15
+ * bad host-supplied value. */
16
+ export declare function hexToRgb(hex: string): Rgb | null;
17
+ export declare function rgbToHex({ r, g, b }: Rgb): string;
18
+ /** Linear-interpolates two colors; t=0 -> a, t=1 -> b. */
19
+ export declare function mix(a: Rgb, b: Rgb, t: number): Rgb;
20
+ /** Mantine's color-tuple shape: exactly 10 shades. */
21
+ export type AccentShades = readonly [string, string, string, string, string, string, string, string, string, string];
22
+ /**
23
+ * A 10-shade Mantine color array for `theme.colors.likhariAccent`, mixing
24
+ * the base color towards white for the lighter shades (0-4ish) and towards
25
+ * black for the darker ones (6-9ish) — shade 6 (Mantine's default "filled"
26
+ * shade) is the exact input color, matching what the static
27
+ * `likhariAccent` ramp already did for the default tokens.
28
+ */
29
+ export declare function generateAccentShades(hex: string): AccentShades | null;
30
+ /**
31
+ * The editor canvas/toolbar's own `--editor-accent` / `--editor-accent-soft`
32
+ * pair for a host-supplied accent color, mirroring the relationship between
33
+ * `LIGHT_TOKENS`/`DARK_TOKENS`'s `accent`/`accentSoft` (packages/core/src/theme/tokens.ts):
34
+ * light mode keeps the accent as given and derives a light tint for "soft";
35
+ * dark mode lightens the accent itself (for contrast against a dark canvas)
36
+ * and derives a dark tint for "soft".
37
+ */
38
+ export declare function generateAccentCssVars(hex: string, mode: 'light' | 'dark'): {
39
+ accent: string;
40
+ accentSoft: string;
41
+ } | null;
42
+ export {};
43
+ //# sourceMappingURL=accentColor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accentColor.d.ts","sourceRoot":"","sources":["../../src/theme/accentColor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,UAAU,GAAG;IACX,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;8BAE8B;AAC9B,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAehD;AAQD,wBAAgB,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,CAEjD;AAED,0DAA0D;AAC1D,wBAAgB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,GAAG,CAMlD;AAKD,sDAAsD;AACtD,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAErH;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAQrE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAaxH"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Derives Mantine's 10-shade color array and the editor's own
3
+ * `--editor-accent` / `--editor-accent-soft` pair from a single
4
+ * host-supplied accent color (EditorRootProps.accentColor). No color
5
+ * library needed — a few lines of hex parsing plus RGB lerp is enough for a
6
+ * usable (not professionally calibrated) ramp.
7
+ */
8
+ /** Parses a `#rgb` or `#rrggbb` hex color. Returns null for anything else —
9
+ * callers fall back to the static default tokens rather than throwing on a
10
+ * bad host-supplied value. */
11
+ export function hexToRgb(hex) {
12
+ const cleaned = hex.trim().replace(/^#/, '');
13
+ const full = cleaned.length === 3
14
+ ? cleaned
15
+ .split('')
16
+ .map((c) => c + c)
17
+ .join('')
18
+ : cleaned;
19
+ if (!/^[0-9a-fA-F]{6}$/.test(full))
20
+ return null;
21
+ return {
22
+ r: parseInt(full.slice(0, 2), 16),
23
+ g: parseInt(full.slice(2, 4), 16),
24
+ b: parseInt(full.slice(4, 6), 16),
25
+ };
26
+ }
27
+ function toHex(n) {
28
+ return Math.round(Math.min(255, Math.max(0, n)))
29
+ .toString(16)
30
+ .padStart(2, '0');
31
+ }
32
+ export function rgbToHex({ r, g, b }) {
33
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
34
+ }
35
+ /** Linear-interpolates two colors; t=0 -> a, t=1 -> b. */
36
+ export function mix(a, b, t) {
37
+ return {
38
+ r: a.r + (b.r - a.r) * t,
39
+ g: a.g + (b.g - a.g) * t,
40
+ b: a.b + (b.b - a.b) * t,
41
+ };
42
+ }
43
+ const WHITE = { r: 255, g: 255, b: 255 };
44
+ const BLACK = { r: 0, g: 0, b: 0 };
45
+ /**
46
+ * A 10-shade Mantine color array for `theme.colors.likhariAccent`, mixing
47
+ * the base color towards white for the lighter shades (0-4ish) and towards
48
+ * black for the darker ones (6-9ish) — shade 6 (Mantine's default "filled"
49
+ * shade) is the exact input color, matching what the static
50
+ * `likhariAccent` ramp already did for the default tokens.
51
+ */
52
+ export function generateAccentShades(hex) {
53
+ const base = hexToRgb(hex);
54
+ if (!base)
55
+ return null;
56
+ // How far to mix towards white (positive) or black (negative) per shade,
57
+ // relative to shade 6 = the exact input color.
58
+ const mixAmount = [0.88, 0.72, 0.56, 0.4, 0.22, 0.1, 0, -0.14, -0.28, -0.42];
59
+ const shades = mixAmount.map((t) => rgbToHex(mix(base, t >= 0 ? WHITE : BLACK, Math.abs(t))));
60
+ return shades;
61
+ }
62
+ /**
63
+ * The editor canvas/toolbar's own `--editor-accent` / `--editor-accent-soft`
64
+ * pair for a host-supplied accent color, mirroring the relationship between
65
+ * `LIGHT_TOKENS`/`DARK_TOKENS`'s `accent`/`accentSoft` (packages/core/src/theme/tokens.ts):
66
+ * light mode keeps the accent as given and derives a light tint for "soft";
67
+ * dark mode lightens the accent itself (for contrast against a dark canvas)
68
+ * and derives a dark tint for "soft".
69
+ */
70
+ export function generateAccentCssVars(hex, mode) {
71
+ const base = hexToRgb(hex);
72
+ if (!base)
73
+ return null;
74
+ if (mode === 'dark') {
75
+ return {
76
+ accent: rgbToHex(mix(base, WHITE, 0.45)),
77
+ accentSoft: rgbToHex(mix(base, BLACK, 0.75)),
78
+ };
79
+ }
80
+ return {
81
+ accent: hex,
82
+ accentSoft: rgbToHex(mix(base, WHITE, 0.88)),
83
+ };
84
+ }
@@ -19,6 +19,12 @@
19
19
  its own height so only the canvas below scrolls. */
20
20
  .likhari-toolbar {
21
21
  display: flex;
22
+ /* Containing block for the hidden width-measuring clone (Toolbar.tsx):
23
+ without this, its position: absolute would resolve against the
24
+ viewport instead, and its intentionally-unwrapped (possibly very wide)
25
+ content could inflate the host page's scroll area instead of being
26
+ clipped by .likhari-root's overflow: hidden. */
27
+ position: relative;
22
28
  flex-shrink: 0;
23
29
  align-items: center;
24
30
  gap: 8px;
@@ -33,6 +39,18 @@
33
39
  row-gap: 4px;
34
40
  }
35
41
 
42
+ /* Empty portal-anchor (EditorRoot.tsx) — exists only so Mantine's portaled
43
+ Menu/Modal/Select-combobox/Tooltip content has a DOM ancestor inside
44
+ .likhari-root/.likhari-theme-scope to inherit --editor-* vars and `dir`
45
+ from; it must take up no space and never intercept pointer events. */
46
+ .likhari-portal-target {
47
+ position: absolute;
48
+ width: 0;
49
+ height: 0;
50
+ overflow: visible;
51
+ pointer-events: none;
52
+ }
53
+
36
54
  .likhari-toolbar-group {
37
55
  display: flex;
38
56
  align-items: center;
@@ -235,6 +253,113 @@
235
253
  margin: 1.5em 0;
236
254
  }
237
255
 
256
+ /* Images — block-level figure; the caption is static text (edited in the
257
+ image's dialog), and a selected image gets a corner resize handle. */
258
+ .likhari-image-block {
259
+ margin: 0 0 1em 0;
260
+ }
261
+
262
+ .likhari-image-figure {
263
+ display: block;
264
+ margin: 0;
265
+ text-align: center;
266
+ }
267
+
268
+ .likhari-image-wrap {
269
+ position: relative;
270
+ display: inline-block;
271
+ max-width: 100%;
272
+ line-height: 0;
273
+ }
274
+
275
+ .likhari-image {
276
+ display: block;
277
+ max-width: 100%;
278
+ height: auto;
279
+ border-radius: 4px;
280
+ cursor: default;
281
+ }
282
+
283
+ .likhari-image-figure[data-selected='true'] .likhari-image {
284
+ outline: 2px solid var(--editor-accent);
285
+ outline-offset: 2px;
286
+ }
287
+
288
+ .likhari-image-resize-handle {
289
+ position: absolute;
290
+ right: -6px;
291
+ bottom: -6px;
292
+ width: 12px;
293
+ height: 12px;
294
+ border: 2px solid var(--editor-paper);
295
+ border-radius: 3px;
296
+ background: var(--editor-accent);
297
+ cursor: nwse-resize;
298
+ touch-action: none;
299
+ }
300
+
301
+ .likhari-image-caption {
302
+ margin-top: 6px;
303
+ color: var(--editor-ink-soft);
304
+ font-size: 0.85em;
305
+ line-height: 1.4;
306
+ text-align: center;
307
+ }
308
+
309
+ /* Image dialog — crop stage. The selection dims everything outside it. */
310
+ .likhari-cropper-stage {
311
+ position: relative;
312
+ display: inline-block;
313
+ align-self: center;
314
+ max-width: 100%;
315
+ padding: 8px;
316
+ overflow: hidden;
317
+ line-height: 0;
318
+ cursor: crosshair;
319
+ touch-action: none;
320
+ user-select: none;
321
+ }
322
+
323
+ .likhari-cropper-frame {
324
+ position: relative;
325
+ display: inline-block;
326
+ max-width: 100%;
327
+ }
328
+
329
+ .likhari-cropper-image {
330
+ display: block;
331
+ max-width: 100%;
332
+ max-height: 320px;
333
+ }
334
+
335
+ .likhari-cropper-selection {
336
+ position: absolute;
337
+ border: 2px solid #fff;
338
+ outline: 1px solid rgba(0, 0, 0, 0.5);
339
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
340
+ cursor: move;
341
+ }
342
+
343
+ .likhari-cropper-handle {
344
+ position: absolute;
345
+ width: 12px;
346
+ height: 12px;
347
+ border: 1px solid rgba(0, 0, 0, 0.6);
348
+ border-radius: 2px;
349
+ background: #fff;
350
+ /* 12px handles on small selections would overlap; keep them on the border. */
351
+ transform: translate(-50%, -50%);
352
+ }
353
+
354
+ .likhari-cropper-handle[data-handle='nw'] { left: 0; top: 0; cursor: nwse-resize; }
355
+ .likhari-cropper-handle[data-handle='n'] { left: 50%; top: 0; cursor: ns-resize; }
356
+ .likhari-cropper-handle[data-handle='ne'] { left: 100%; top: 0; cursor: nesw-resize; }
357
+ .likhari-cropper-handle[data-handle='e'] { left: 100%; top: 50%; cursor: ew-resize; }
358
+ .likhari-cropper-handle[data-handle='se'] { left: 100%; top: 100%; cursor: nwse-resize; }
359
+ .likhari-cropper-handle[data-handle='s'] { left: 50%; top: 100%; cursor: ns-resize; }
360
+ .likhari-cropper-handle[data-handle='sw'] { left: 0; top: 100%; cursor: nesw-resize; }
361
+ .likhari-cropper-handle[data-handle='w'] { left: 0; top: 50%; cursor: ew-resize; }
362
+
238
363
  /* Link menu: the URL item shows one truncated line. */
239
364
  .likhari-link-menu-url {
240
365
  max-width: 100%;
@@ -1 +1 @@
1
- {"version":3,"file":"editorTheme.d.ts","sourceRoot":"","sources":["../../src/theme/editorTheme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,kBA6BzB,CAAC"}
1
+ {"version":3,"file":"editorTheme.d.ts","sourceRoot":"","sources":["../../src/theme/editorTheme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,kBA8BzB,CAAC"}
@@ -32,4 +32,5 @@ export const editorTheme = {
32
32
  superscript: 'likhari-text-superscript',
33
33
  },
34
34
  hr: 'likhari-hr',
35
+ image: 'likhari-image-block',
35
36
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.6",
3
+ "version": "0.1.7-dev.73",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,8 +13,10 @@
13
13
  "exports": {
14
14
  ".": {
15
15
  "types": "./dist/index.d.ts",
16
+ "development": "./src/index.ts",
16
17
  "import": "./dist/index.js"
17
18
  },
19
+ "./styles.css": "./dist/theme/editor.css",
18
20
  "./package.json": "./package.json"
19
21
  },
20
22
  "files": [
@@ -39,13 +41,17 @@
39
41
  "@lexical/utils": "^0.20.0",
40
42
  "@inshapardaz/likhari-converters": "^0.1.0",
41
43
  "@inshapardaz/likhari-core": "^0.1.0",
42
- "@mantine/core": "^9.6.3",
43
- "@mantine/hooks": "^9.6.3",
44
44
  "@tabler/icons-react": "^3.46.0",
45
45
  "lexical": "^0.20.0"
46
46
  },
47
47
  "peerDependencies": {
48
+ "@mantine/core": "^9.6.3",
49
+ "@mantine/hooks": "^9.6.3",
48
50
  "react": "^19.2.0",
49
51
  "react-dom": "^19.2.0"
52
+ },
53
+ "devDependencies": {
54
+ "@mantine/core": "^9.6.3",
55
+ "@mantine/hooks": "^9.6.3"
50
56
  }
51
57
  }