@nice2dev/ui-graphics 1.1.10 → 1.2.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 (113) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +60 -63
  3. package/dist/cjs/animation/AnimationEditor.js +0 -3
  4. package/dist/cjs/animation/Audience.js +3 -1
  5. package/dist/cjs/animation/BodyRenderer.js +1 -4
  6. package/dist/cjs/animation/rig/RigPlayer.js +1 -1
  7. package/dist/cjs/animation/rig/presets.js +67 -55
  8. package/dist/cjs/animation/shapes/heads.js +0 -3
  9. package/dist/cjs/animation/shapes/headwear.js +4 -1
  10. package/dist/cjs/core/NiceBrushEngine.js +6 -6
  11. package/dist/cjs/core/editorAsk.js +48 -0
  12. package/dist/cjs/core/i18n.js +7 -3
  13. package/dist/cjs/core/plugins.js +1 -1
  14. package/dist/cjs/core/rtl.js +1 -1
  15. package/dist/cjs/core/theme.js +1 -1
  16. package/dist/cjs/font/NiceFontEditor.js +16 -14
  17. package/dist/cjs/game/GameAsset2dEditor.js +28 -19
  18. package/dist/cjs/index.js +0 -11
  19. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  20. package/dist/cjs/photo/FilterThumb.js +3 -3
  21. package/dist/cjs/photo/NiceAITools.js +7 -5
  22. package/dist/cjs/photo/PhotoEditor.js +3 -1
  23. package/dist/cjs/photo/PhotoEditorCanvas.js +8 -6
  24. package/dist/cjs/photo/PhotoEditorLeftPanel.js +19 -19
  25. package/dist/cjs/photo/PhotoEditorToolbar.js +5 -5
  26. package/dist/cjs/photo/photoFilters.js +1 -0
  27. package/dist/cjs/pixel/PixelEditor.js +6 -6
  28. package/dist/cjs/pixel/PixelEditor.module.css.js +1 -1
  29. package/dist/cjs/pixel/PixelEditorCanvas.js +10 -6
  30. package/dist/cjs/pixel/PixelEditorMenuBar.js +3 -3
  31. package/dist/cjs/pixel/PixelEditorRightPanel.js +28 -24
  32. package/dist/cjs/pixel/PixelEditorStatusBar.js +8 -4
  33. package/dist/cjs/pixel/PixelEditorTimeline.js +11 -2
  34. package/dist/cjs/pixel/PixelEditorToolbar.js +10 -6
  35. package/dist/cjs/pixel/SpriteGeneratorPanel.js +12 -8
  36. package/dist/cjs/pixel/animalGeneratorPresets.js +31 -31
  37. package/dist/cjs/pixel/buildingGeneratorPresets.js +24 -24
  38. package/dist/cjs/pixel/characterGeneratorPresets.js +25 -25
  39. package/dist/cjs/pixel/usePixelEditor.js +13 -10
  40. package/dist/cjs/texture/Nice3DTexturePainter.js +12 -10
  41. package/dist/cjs/ui/NiceUIDesigner.js +18 -14
  42. package/dist/cjs/vector/NiceSvgToCode.js +6 -4
  43. package/dist/cjs/vector/VectorEditor.js +3 -5
  44. package/dist/cjs/vector/VectorEditorShapeRenderer.js +3 -1
  45. package/dist/cjs/vector/useVectorEditor.js +38 -29
  46. package/dist/cjs/vector/vectorEditorImport.js +1 -0
  47. package/dist/cjs/vector/vectorIconImport.js +1 -0
  48. package/dist/esm/animation/AnimationEditor.js +1 -3
  49. package/dist/esm/animation/Audience.js +3 -1
  50. package/dist/esm/animation/BodyRenderer.js +3 -5
  51. package/dist/esm/animation/rig/RigPlayer.js +1 -1
  52. package/dist/esm/animation/rig/presets.js +68 -56
  53. package/dist/esm/animation/shapes/heads.js +1 -3
  54. package/dist/esm/animation/shapes/headwear.js +4 -1
  55. package/dist/esm/animation/spineExport.js +1 -1
  56. package/dist/esm/core/NiceBrushEngine.js +6 -6
  57. package/dist/esm/core/editorAsk.js +45 -0
  58. package/dist/esm/core/i18n.js +8 -4
  59. package/dist/esm/core/plugins.js +1 -1
  60. package/dist/esm/core/rtl.js +1 -1
  61. package/dist/esm/core/theme.js +1 -1
  62. package/dist/esm/font/NiceFontEditor.js +16 -14
  63. package/dist/esm/game/GameAsset2dEditor.js +29 -19
  64. package/dist/esm/index.js +10 -10
  65. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  66. package/dist/esm/photo/FilterThumb.js +3 -3
  67. package/dist/esm/photo/NiceAITools.js +7 -5
  68. package/dist/esm/photo/PhotoEditor.js +3 -1
  69. package/dist/esm/photo/PhotoEditorCanvas.js +8 -6
  70. package/dist/esm/photo/PhotoEditorLeftPanel.js +19 -19
  71. package/dist/esm/photo/PhotoEditorToolbar.js +5 -5
  72. package/dist/esm/photo/photoFilters.js +1 -0
  73. package/dist/esm/pixel/PixelEditor.js +7 -6
  74. package/dist/esm/pixel/PixelEditor.module.css.js +1 -1
  75. package/dist/esm/pixel/PixelEditorCanvas.js +10 -6
  76. package/dist/esm/pixel/PixelEditorMenuBar.js +3 -3
  77. package/dist/esm/pixel/PixelEditorRightPanel.js +28 -24
  78. package/dist/esm/pixel/PixelEditorStatusBar.js +8 -4
  79. package/dist/esm/pixel/PixelEditorTimeline.js +11 -2
  80. package/dist/esm/pixel/PixelEditorToolbar.js +10 -6
  81. package/dist/esm/pixel/SpriteGeneratorPanel.js +12 -8
  82. package/dist/esm/pixel/animalGeneratorPresets.js +32 -32
  83. package/dist/esm/pixel/buildingGeneratorPresets.js +25 -25
  84. package/dist/esm/pixel/characterGeneratorPresets.js +26 -26
  85. package/dist/esm/pixel/usePixelEditor.js +13 -10
  86. package/dist/esm/texture/Nice3DTexturePainter.js +12 -10
  87. package/dist/esm/ui/NiceUIDesigner.js +18 -14
  88. package/dist/esm/vector/NiceSvgToCode.js +6 -4
  89. package/dist/esm/vector/VectorEditor.js +4 -5
  90. package/dist/esm/vector/VectorEditorShapeRenderer.js +3 -1
  91. package/dist/esm/vector/useVectorEditor.js +38 -29
  92. package/dist/esm/vector/vectorEditorImport.js +1 -0
  93. package/dist/esm/vector/vectorIconImport.js +1 -0
  94. package/dist/types/animation/AnimatedPerson.d.ts +0 -4
  95. package/dist/types/animation/AnimationEditor.d.ts +1 -5
  96. package/dist/types/animation/Audience.d.ts +0 -4
  97. package/dist/types/animation/BodyRenderer.d.ts +0 -4
  98. package/dist/types/animation/rig/RigPlayer.d.ts +0 -7
  99. package/dist/types/animation/rig/RiggedBody.d.ts +0 -4
  100. package/dist/types/animation/shapes/heads.d.ts +0 -4
  101. package/dist/types/core/LocalUI.d.ts +1 -2
  102. package/dist/types/core/editorAsk.d.ts +26 -0
  103. package/dist/types/core/i18n.d.ts +1 -17
  104. package/dist/types/game/GameAsset2dEditor.d.ts +0 -4
  105. package/dist/types/game/index.d.ts +1 -1
  106. package/dist/types/index.d.ts +9 -19
  107. package/dist/types/photo/index.d.ts +0 -2
  108. package/dist/types/pixel/PixelEditor.d.ts +0 -4
  109. package/dist/types/pixel/SpriteGeneratorPanel.d.ts +0 -4
  110. package/dist/types/pixel/usePixelEditor.d.ts +3 -0
  111. package/dist/types/vector/VectorEditor.d.ts +0 -4
  112. package/dist/types/vector/useVectorEditor.d.ts +2 -0
  113. package/package.json +5 -5
@@ -10,13 +10,16 @@ const HeadwearCap = ({ colors, scale = 1, transform }) => {
10
10
  };
11
11
  const HeadwearCrown = ({ colors, scale = 1, transform }) => {
12
12
  const base = characterTypes.pick(colors, 0, 'var(--nice-warning, #FFD700)');
13
- return (jsxRuntime.jsx("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: jsxRuntime.jsx("path", { d: "M-22 -30 l6 -16 l8 10 l8 -14 l8 14 l8 -10 l6 16 z", fill: base, stroke: "#B8860B", strokeWidth: 1.2 }) }));
13
+ return (jsxRuntime.jsx("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: jsxRuntime.jsx("path", { d: "M-22 -30 l6 -16 l8 10 l8 -14 l8 14 l8 -10 l6 16 z", fill: base,
14
+ // audit-allow: literal-color — a character-generator shape: its own drawing colour
15
+ stroke: "#B8860B", strokeWidth: 1.2 }) }));
14
16
  };
15
17
  const HeadwearHeadphones = ({ colors, scale = 1, transform }) => {
16
18
  const base = characterTypes.pick(colors, 0, 'var(--nice-text, #333)');
17
19
  return (jsxRuntime.jsxs("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, stroke: base, strokeWidth: 2.5, fill: "none", children: [jsxRuntime.jsx("path", { d: "M-30 0 q0 -40 30 -42 q30 2 30 42" }), jsxRuntime.jsx("rect", { x: -36, y: -6, width: 10, height: 16, rx: 3, fill: base }), jsxRuntime.jsx("rect", { x: 26, y: -6, width: 10, height: 16, rx: 3, fill: base })] }));
18
20
  };
19
21
  const HeadwearBeanie = ({ colors, scale = 1, transform }) => {
22
+ // audit-allow: literal-color — the hat's default colour when the character sets none
20
23
  const base = characterTypes.pick(colors, 0, '#E53E3E');
21
24
  return (jsxRuntime.jsxs("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: [jsxRuntime.jsx("path", { d: "M-28 -14 q0 -22 28 -26 q28 4 28 26 z", fill: base }), jsxRuntime.jsx("circle", { cx: 0, cy: -42, r: 6, fill: base })] }));
22
25
  };
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var uiCore = require('@nice2dev/ui-core');
5
5
  var React = require('react');
6
+ var i18n = require('./i18n.js');
6
7
 
7
8
  /* ── Brush Presets ── */
8
9
  const DEFAULT_DYNAMICS = {
@@ -543,6 +544,7 @@ color = '#000000') {
543
544
  }
544
545
  /* ── Component ── */
545
546
  const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '', }) => {
547
+ const { t } = i18n.useNiceTranslation();
546
548
  const engine = useBrushEngine(canvas, color);
547
549
  // Override endStroke to fire callback
548
550
  React.useEffect(() => {
@@ -559,7 +561,7 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
559
561
  return () => canvas.removeEventListener('pointerup', onUp);
560
562
  }, [canvas, engine.endStroke, onStrokeEnd]);
561
563
  const s = engine.settings;
562
- return (jsxRuntime.jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "brush", size: 14 }), " Brush Engine v2"] }), jsxRuntime.jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: engine.presetNames.map((name) => {
564
+ return (jsxRuntime.jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "brush", size: 14 }), " ", t('brushEngine.brushEngineV2', 'Brush Engine v2')] }), jsxRuntime.jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: engine.presetNames.map((name) => {
563
565
  var _a, _b;
564
566
  return (jsxRuntime.jsx("button", { type: "button", onClick: () => engine.applyPreset(name), style: {
565
567
  padding: '2px 6px',
@@ -570,17 +572,15 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
570
572
  background: s.name === ((_a = BRUSH_PRESETS[name]) === null || _a === void 0 ? void 0 : _a.name)
571
573
  ? 'var(--nice-color-accent, #6366f1)'
572
574
  : 'transparent',
573
- color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name)
574
- ? 'var(--nice-text-on-primary, #fff)'
575
- : 'inherit',
575
+ color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? 'var(--nice-color-white, #fff)' : 'inherit',
576
576
  }, children: BRUSH_PRESETS[name].name }, name));
577
- }) }), jsxRuntime.jsxs("label", { children: ["Size: ", s.size, "px", jsxRuntime.jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Opacity: ", Math.round(s.opacity * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.opacity, onChange: (e) => engine.setSettings({ ...s, opacity: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Flow: ", Math.round(s.flow * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.flow, onChange: (e) => engine.setSettings({ ...s, flow: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Hardness: ", Math.round(s.hardness * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.hardness, onChange: (e) => engine.setSettings({ ...s, hardness: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Smoothing: ", Math.round(s.smoothing * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.smoothing, onChange: (e) => engine.setSettings({ ...s, smoothing: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Tip:", jsxRuntime.jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "circle", children: "Circle" }), jsxRuntime.jsx("option", { value: "square", children: "Square" }), jsxRuntime.jsx("option", { value: "diamond", children: "Diamond" }), jsxRuntime.jsx("option", { value: "texture", children: "Texture" })] })] }), jsxRuntime.jsxs("label", { children: ["Roundness: ", Math.round(s.roundness * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: s.roundness, onChange: (e) => engine.setSettings({ ...s, roundness: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("details", { children: [jsxRuntime.jsxs("summary", { style: { cursor: 'pointer', fontSize: 12 }, children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "bar-chart", size: 12 }), " Size Dynamics"] }), jsxRuntime.jsxs("label", { children: ["Source:", jsxRuntime.jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
577
+ }) }), jsxRuntime.jsxs("label", { children: ["Size: ", s.size, "px", jsxRuntime.jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Opacity: ", Math.round(s.opacity * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.opacity, onChange: (e) => engine.setSettings({ ...s, opacity: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Flow: ", Math.round(s.flow * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.flow, onChange: (e) => engine.setSettings({ ...s, flow: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Hardness: ", Math.round(s.hardness * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.hardness, onChange: (e) => engine.setSettings({ ...s, hardness: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Smoothing: ", Math.round(s.smoothing * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.smoothing, onChange: (e) => engine.setSettings({ ...s, smoothing: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Tip:", jsxRuntime.jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "circle", children: t('brushEngine.circle', 'Circle') }), jsxRuntime.jsx("option", { value: "square", children: t('brushEngine.square', 'Square') }), jsxRuntime.jsx("option", { value: "diamond", children: t('brushEngine.diamond', 'Diamond') }), jsxRuntime.jsx("option", { value: "texture", children: t('brushEngine.texture', 'Texture') })] })] }), jsxRuntime.jsxs("label", { children: ["Roundness: ", Math.round(s.roundness * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: s.roundness, onChange: (e) => engine.setSettings({ ...s, roundness: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("details", { children: [jsxRuntime.jsxs("summary", { style: { cursor: 'pointer', fontSize: 12 }, children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "bar-chart", size: 12 }), " ", t('brushEngine.sizeDynamics', 'Size Dynamics')] }), jsxRuntime.jsxs("label", { children: ["Source:", jsxRuntime.jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
578
578
  ...s,
579
579
  sizeDynamics: {
580
580
  ...s.sizeDynamics,
581
581
  source: e.target.value,
582
582
  },
583
- }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "none", children: "Off" }), jsxRuntime.jsx("option", { value: "pressure", children: "Pressure" }), jsxRuntime.jsx("option", { value: "tilt", children: "Tilt" }), jsxRuntime.jsx("option", { value: "speed", children: "Speed" }), jsxRuntime.jsx("option", { value: "random", children: "Random" })] })] })] }), jsxRuntime.jsxs("label", { children: ["Scatter: ", Math.round(s.scatter * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.scatter, onChange: (e) => engine.setSettings({ ...s, scatter: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Blend:", jsxRuntime.jsx("select", { value: s.blendMode, onChange: (e) => engine.setSettings({ ...s, blendMode: e.target.value }), style: { width: '100%' }, children: ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten'].map((m) => (jsxRuntime.jsx("option", { value: m, children: m }, m))) })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: s.wetEdges, onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked }) }), "Wet Edges"] })] }));
583
+ }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "none", children: "Off" }), jsxRuntime.jsx("option", { value: "pressure", children: t('brushEngine.pressure', 'Pressure') }), jsxRuntime.jsx("option", { value: "tilt", children: t('brushEngine.tilt', 'Tilt') }), jsxRuntime.jsx("option", { value: "speed", children: t('brushEngine.speed', 'Speed') }), jsxRuntime.jsx("option", { value: "random", children: t('brushEngine.random', 'Random') })] })] })] }), jsxRuntime.jsxs("label", { children: ["Scatter: ", Math.round(s.scatter * 100), "%", jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.scatter, onChange: (e) => engine.setSettings({ ...s, scatter: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: ["Blend:", jsxRuntime.jsx("select", { value: s.blendMode, onChange: (e) => engine.setSettings({ ...s, blendMode: e.target.value }), style: { width: '100%' }, children: ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten'].map((m) => (jsxRuntime.jsx("option", { value: m, children: m }, m))) })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: s.wetEdges, onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked }) }), t('brushEngine.wetEdges', 'Wet Edges')] })] }));
584
584
  };
585
585
 
586
586
  exports.BRUSH_PRESETS = BRUSH_PRESETS;
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var ui = require('@nice2dev/ui');
5
+ var React = require('react');
6
+
7
+ /**
8
+ * The library's dialogs in place of `window.prompt` / `window.confirm` for the editor hooks,
9
+ * which hold state but render nothing: `ask` resolves with the answer (`null` when cancelled)
10
+ * and `pendingAsk` is the open question, for the editor component to pass to
11
+ * {@link EditorAskDialog}. One question at a time: a new one settles the open one as cancelled.
12
+ */
13
+ function useEditorAsk() {
14
+ const [pendingAsk, setPendingAsk] = React.useState(null);
15
+ const pendingRef = React.useRef(null);
16
+ const ask = React.useCallback((kind, text, defaultValue) => new Promise((resolve) => {
17
+ var _a;
18
+ (_a = pendingRef.current) === null || _a === void 0 ? void 0 : _a.answer(null);
19
+ const next = {
20
+ kind,
21
+ text,
22
+ defaultValue,
23
+ answer: (value) => {
24
+ if (pendingRef.current === next) {
25
+ pendingRef.current = null;
26
+ setPendingAsk(null);
27
+ }
28
+ resolve(value);
29
+ },
30
+ };
31
+ pendingRef.current = next;
32
+ setPendingAsk(next);
33
+ }), []);
34
+ return { pendingAsk, ask };
35
+ }
36
+ /** Shows the question {@link useEditorAsk} is waiting on, if any. */
37
+ const EditorAskDialog = ({ ask }) => {
38
+ if (!ask) {
39
+ return null;
40
+ }
41
+ if (ask.kind === 'confirm') {
42
+ return (jsxRuntime.jsx(ui.NiceConfirmDialog, { visible: true, message: ask.text, onAccept: () => ask.answer(''), onReject: () => ask.answer(null) }));
43
+ }
44
+ return (jsxRuntime.jsx(ui.NicePromptDialog, { open: true, title: ask.text, defaultValue: ask.defaultValue, onSubmit: (value) => ask.answer(value), onCancel: () => ask.answer(null) }));
45
+ };
46
+
47
+ exports.EditorAskDialog = EditorAskDialog;
48
+ exports.useEditorAsk = useEditorAsk;
@@ -1,13 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
+ var uiCore = require('@nice2dev/ui-core');
4
5
  var React = require('react');
5
6
 
6
- const fallback = (_key, defaultValue) => defaultValue;
7
- const NiceI18nContext = React.createContext(fallback);
7
+ /** `null` outside this package's provider: the global translator applies. */
8
+ const NiceI18nContext = /* @__PURE__ */ React.createContext(null);
9
+ /** Overrides the app's global translator for the graphics controls below it. */
8
10
  const NiceI18nProvider = ({ t, children }) => (jsxRuntime.jsx(NiceI18nContext.Provider, { value: t, children: children }));
9
11
  function useNiceTranslation() {
10
- const t = React.useContext(NiceI18nContext);
12
+ const own = React.useContext(NiceI18nContext);
13
+ const { t: global } = uiCore.useNiceTranslation();
14
+ const t = React.useMemo(() => own !== null && own !== void 0 ? own : ((key, defaultValue) => global(key, defaultValue)), [own, global]);
11
15
  return { t };
12
16
  }
13
17
 
@@ -112,7 +112,7 @@ function usePlugins(editor) {
112
112
  };
113
113
  }
114
114
  // ── Plugin Context (React) ──────────────────────────────────────
115
- const PluginContext = React.createContext(PluginRegistry);
115
+ const PluginContext = /* @__PURE__ */ React.createContext(PluginRegistry);
116
116
  const NicePluginProvider = ({ plugins, children }) => {
117
117
  React.useEffect(() => {
118
118
  if (plugins) {
@@ -14,7 +14,7 @@ const defaultRTLConfig = {
14
14
  mirrorToolbar: false,
15
15
  mirrorPanels: false,
16
16
  };
17
- const RTLContext = React.createContext(defaultRTLConfig);
17
+ const RTLContext = /* @__PURE__ */ React.createContext(defaultRTLConfig);
18
18
  /** Provider for RTL configuration. Wraps editors to enable RTL support. */
19
19
  const NiceRTLProvider = ({ direction = 'ltr', mirrorToolbar = true, mirrorPanels = true, children }) => {
20
20
  const config = React.useMemo(() => ({
@@ -109,7 +109,7 @@ function resolveThemeMode(mode) {
109
109
  }
110
110
  return mode;
111
111
  }
112
- const ThemeContext = React.createContext({
112
+ const ThemeContext = /* @__PURE__ */ React.createContext({
113
113
  theme: DARK_THEME,
114
114
  mode: 'dark',
115
115
  setMode: () => { },
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var uiCore = require('@nice2dev/ui-core');
6
6
  var React = require('react');
7
+ var i18n = require('../core/i18n.js');
7
8
 
8
9
  /** Built-in guided tour (EN inline + i18n keys → PL and other locales). */
9
10
  const FONT_EDITOR_TOUR = [
@@ -134,6 +135,7 @@ function useFontEditor(initialName) {
134
135
  /** Font glyph editor with metrics, visual path editing, and preview. */
135
136
  const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial, }) => {
136
137
  var _a;
138
+ const { t } = i18n.useNiceTranslation();
137
139
  const editor = useFontEditor(fontName);
138
140
  const [drawMode, setDrawMode] = React.useState('select');
139
141
  const [pathPoints, setPathPoints] = React.useState([]);
@@ -191,7 +193,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
191
193
  border: 'none',
192
194
  cursor: 'pointer',
193
195
  background: active ? 'var(--nice-info, #339af0)' : 'var(--nice-bg-secondary, #2c2e33)',
194
- color: active ? 'var(--nice-text-on-primary, #fff)' : 'var(--nice-text, #c1c2c5)',
196
+ color: active ? 'var(--nice-color-white, #fff)' : 'var(--nice-text, #c1c2c5)',
195
197
  fontSize: 11,
196
198
  });
197
199
  const fieldStyle = {
@@ -221,7 +223,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
221
223
  color: 'var(--nice-text, #c1c2c5)',
222
224
  padding: 12,
223
225
  borderRadius: 8,
224
- }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, FONT_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 14 }, children: "Font Editor" }), jsxRuntime.jsx("input", { value: editor.fontName, onChange: (e) => editor.setFontName(e.target.value), placeholder: "Font name", style: { flex: 1, maxWidth: 200, ...fieldStyle, fontSize: 12 } }), jsxRuntime.jsx("button", { onClick: handleSave, style: {
226
+ }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, FONT_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 14 }, children: t('fontEditor.fontEditor', 'Font Editor') }), jsxRuntime.jsx("input", { value: editor.fontName, onChange: (e) => editor.setFontName(e.target.value), placeholder: t('fontEditor.fontName', 'Font name'), style: { flex: 1, maxWidth: 200, ...fieldStyle, fontSize: 12 } }), jsxRuntime.jsx("button", { onClick: handleSave, style: {
225
227
  padding: '4px 12px',
226
228
  background: 'var(--nice-success, #2f9e44)',
227
229
  color: 'var(--nice-text-on-primary, #fff)',
@@ -229,15 +231,15 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
229
231
  borderRadius: 3,
230
232
  cursor: 'pointer',
231
233
  fontSize: 11,
232
- }, children: "Save" }), jsxRuntime.jsx("button", { onClick: handleExportSvg, style: {
234
+ }, children: t('fontEditor.save', 'Save') }), jsxRuntime.jsx("button", { onClick: handleExportSvg, style: {
233
235
  padding: '4px 12px',
234
236
  background: '#228be6',
235
- color: 'var(--nice-text-on-primary, #fff)',
237
+ color: 'var(--nice-color-white, #fff)',
236
238
  border: 'none',
237
239
  borderRadius: 3,
238
240
  cursor: 'pointer',
239
241
  fontSize: 11,
240
- }, children: "Export SVG Font" })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, borderBottom: '1px solid var(--nice-border, #373a40)' }, children: [jsxRuntime.jsx("button", { onClick: () => setTab('glyph'), style: tabBtnStyle(tab === 'glyph'), children: "Glyphs" }), jsxRuntime.jsx("button", { onClick: () => setTab('metrics'), style: tabBtnStyle(tab === 'metrics'), children: "Metrics" }), jsxRuntime.jsx("button", { onClick: () => setTab('kerning'), style: tabBtnStyle(tab === 'kerning'), children: "Kerning" })] }), tab === 'glyph' && (jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsxs("div", { style: {
242
+ }, children: t('fontEditor.exportSvgFont', 'Export SVG Font') })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, borderBottom: '1px solid var(--nice-border, #373a40)' }, children: [jsxRuntime.jsx("button", { onClick: () => setTab('glyph'), style: tabBtnStyle(tab === 'glyph'), children: t('fontEditor.glyphs', 'Glyphs') }), jsxRuntime.jsx("button", { onClick: () => setTab('metrics'), style: tabBtnStyle(tab === 'metrics'), children: t('fontEditor.metrics', 'Metrics') }), jsxRuntime.jsx("button", { onClick: () => setTab('kerning'), style: tabBtnStyle(tab === 'kerning'), children: t('fontEditor.kerning', 'Kerning') })] }), tab === 'glyph' && (jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsxs("div", { style: {
241
243
  fontSize: 11,
242
244
  marginBottom: 4,
243
245
  color: 'var(--nice-text-secondary, #868e96)',
@@ -265,14 +267,14 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
265
267
  ? 'var(--nice-bg-secondary, #2c2e33)'
266
268
  : '#25262b',
267
269
  color: editor.selectedGlyph === g.unicode
268
- ? 'var(--nice-text-on-primary, #fff)'
270
+ ? 'var(--nice-color-white, #fff)'
269
271
  : g.path
270
272
  ? 'var(--nice-text, #c1c2c5)'
271
273
  : '#5c5f66',
272
274
  border: g.path
273
275
  ? '1px solid var(--nice-border, #373a40)'
274
276
  : '1px solid transparent',
275
- }, children: g.char }, g.unicode))) })] }), jsxRuntime.jsx("div", { style: { width: 320, fontSize: 11 }, children: selectedG ? (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: ["\"", selectedG.char, "\" (U+", selectedG.unicode.toString(16).toUpperCase().padStart(4, '0'), ")"] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsxRuntime.jsx("button", { onClick: () => setDrawMode('select'), style: btnStyle(drawMode === 'select'), children: "Select" }), jsxRuntime.jsx("button", { onClick: () => setDrawMode('pen'), style: btnStyle(drawMode === 'pen'), children: "Pen" }), jsxRuntime.jsx("button", { onClick: clearPath, style: btnStyle(false), children: "Clear" }), jsxRuntime.jsx("button", { onClick: () => setShowGrid((g) => !g), style: btnStyle(showGrid), children: "Grid" })] })] }), jsxRuntime.jsxs("svg", { width: 280, height: 280, viewBox: `0 0 ${em} ${em}`, style: {
277
+ }, children: g.char }, g.unicode))) })] }), jsxRuntime.jsx("div", { style: { width: 320, fontSize: 11 }, children: selectedG ? (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: ["\"", selectedG.char, "\" (U+", selectedG.unicode.toString(16).toUpperCase().padStart(4, '0'), ")"] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsxRuntime.jsx("button", { onClick: () => setDrawMode('select'), style: btnStyle(drawMode === 'select'), children: t('fontEditor.select', 'Select') }), jsxRuntime.jsx("button", { onClick: () => setDrawMode('pen'), style: btnStyle(drawMode === 'pen'), children: "Pen" }), jsxRuntime.jsx("button", { onClick: clearPath, style: btnStyle(false), children: t('fontEditor.clear', 'Clear') }), jsxRuntime.jsx("button", { onClick: () => setShowGrid((g) => !g), style: btnStyle(showGrid), children: t('fontEditor.grid', 'Grid') })] })] }), jsxRuntime.jsxs("svg", { width: 280, height: 280, viewBox: `0 0 ${em} ${em}`, style: {
276
278
  background: 'var(--nice-bg, #141517)',
277
279
  borderRadius: 4,
278
280
  cursor: drawMode === 'pen' ? 'crosshair' : 'default',
@@ -280,32 +282,32 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
280
282
  Array.from({ length: 11 }, (_, i) => {
281
283
  const pos = (i / 10) * em;
282
284
  return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("line", { x1: pos, y1: 0, x2: pos, y2: em, stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))", strokeWidth: em * 0.002 }), jsxRuntime.jsx("line", { x1: 0, y1: pos, x2: em, y2: pos, stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))", strokeWidth: em * 0.002 })] }, i));
283
- }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "rgba(51,154,240,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "rgba(47,158,68,0.3)", strokeWidth: em * 0.003 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "rgba(249,226,175,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "rgba(243,139,168,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: selectedG.leftBearing, y1: 0, x2: selectedG.leftBearing, y2: em, stroke: "rgba(166,227,161,0.2)", strokeWidth: em * 0.002 }), jsxRuntime.jsx("line", { x1: selectedG.advanceWidth, y1: 0, x2: selectedG.advanceWidth, y2: em, stroke: "rgba(243,139,168,0.2)", strokeWidth: em * 0.002 }), selectedG.path && (jsxRuntime.jsx("path", { d: selectedG.path, fill: "rgba(205,214,244,0.8)", stroke: "#89b4fa", strokeWidth: em * 0.005 })), pathPoints.map((p, i) => (jsxRuntime.jsx("circle", { cx: p.x, cy: p.y, r: em * 0.015, fill: i === 0 ? 'var(--nice-success, #2f9e44)' : 'var(--nice-info, #339af0)', stroke: "var(--nice-bg, #fff)", strokeWidth: em * 0.003 }, i))), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.ascent - em * 0.01, fill: "rgba(51,154,240,0.5)", fontSize: em * 0.03, children: "ascent" }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.xHeight - em * 0.01, fill: "rgba(249,226,175,0.5)", fontSize: em * 0.03, children: "x-height" }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.capHeight - em * 0.01, fill: "rgba(243,139,168,0.5)", fontSize: em * 0.03, children: "cap" })] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { children: "SVG Path" }), jsxRuntime.jsx("textarea", { value: selectedG.path, onChange: (e) => editor.updateGlyph(selectedG.unicode, { path: e.target.value }), rows: 3, style: { ...fieldStyle, fontFamily: 'monospace', resize: 'vertical' } })] }), jsxRuntime.jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [jsxRuntime.jsx("label", { children: "Advance Width" }), jsxRuntime.jsx("input", { type: "number", value: selectedG.advanceWidth, onChange: (e) => editor.updateGlyph(selectedG.unicode, {
285
+ }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "rgba(51,154,240,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "rgba(47,158,68,0.3)", strokeWidth: em * 0.003 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "rgba(249,226,175,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "rgba(243,139,168,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsxRuntime.jsx("line", { x1: selectedG.leftBearing, y1: 0, x2: selectedG.leftBearing, y2: em, stroke: "rgba(166,227,161,0.2)", strokeWidth: em * 0.002 }), jsxRuntime.jsx("line", { x1: selectedG.advanceWidth, y1: 0, x2: selectedG.advanceWidth, y2: em, stroke: "rgba(243,139,168,0.2)", strokeWidth: em * 0.002 }), selectedG.path && (jsxRuntime.jsx("path", { d: selectedG.path, fill: "rgba(205,214,244,0.8)", stroke: "#89b4fa", strokeWidth: em * 0.005 })), pathPoints.map((p, i) => (jsxRuntime.jsx("circle", { cx: p.x, cy: p.y, r: em * 0.015, fill: i === 0 ? 'var(--nice-success, #2f9e44)' : 'var(--nice-info, #339af0)', stroke: "var(--nice-bg, #fff)", strokeWidth: em * 0.003 }, i))), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.ascent - em * 0.01, fill: "rgba(51,154,240,0.5)", fontSize: em * 0.03, children: "ascent" }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.xHeight - em * 0.01, fill: "rgba(249,226,175,0.5)", fontSize: em * 0.03, children: "x-height" }), jsxRuntime.jsx("text", { x: em * 0.02, y: em - editor.metrics.capHeight - em * 0.01, fill: "rgba(243,139,168,0.5)", fontSize: em * 0.03, children: "cap" })] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { children: t('fontEditor.svgPath', 'SVG Path') }), jsxRuntime.jsx("textarea", { value: selectedG.path, onChange: (e) => editor.updateGlyph(selectedG.unicode, { path: e.target.value }), rows: 3, style: { ...fieldStyle, fontFamily: 'monospace', resize: 'vertical' } })] }), jsxRuntime.jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [jsxRuntime.jsx("label", { children: t('fontEditor.advanceWidth', 'Advance Width') }), jsxRuntime.jsx("input", { type: "number", value: selectedG.advanceWidth, onChange: (e) => editor.updateGlyph(selectedG.unicode, {
284
286
  advanceWidth: Number(e.target.value),
285
- }), style: fieldStyle }), jsxRuntime.jsx("label", { children: "Left Bearing" }), jsxRuntime.jsx("input", { type: "number", value: selectedG.leftBearing, onChange: (e) => editor.updateGlyph(selectedG.unicode, { leftBearing: Number(e.target.value) }), style: fieldStyle })] })] })) : (jsxRuntime.jsx("div", { style: {
287
+ }), style: fieldStyle }), jsxRuntime.jsx("label", { children: t('fontEditor.leftBearing', 'Left Bearing') }), jsxRuntime.jsx("input", { type: "number", value: selectedG.leftBearing, onChange: (e) => editor.updateGlyph(selectedG.unicode, { leftBearing: Number(e.target.value) }), style: fieldStyle })] })] })) : (jsxRuntime.jsx("div", { style: {
286
288
  color: 'var(--nice-text-secondary, #868e96)',
287
289
  padding: 20,
288
290
  textAlign: 'center',
289
- }, children: "Select a glyph to edit" })) })] })), tab === 'metrics' && (jsxRuntime.jsxs("div", { style: { maxWidth: 400 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: "Font Metrics" }), [
291
+ }, children: t('fontEditor.selectAGlyphToEdit', 'Select a glyph to edit') })) })] })), tab === 'metrics' && (jsxRuntime.jsxs("div", { style: { maxWidth: 400 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: t('fontEditor.fontMetrics', 'Font Metrics') }), [
290
292
  ['unitsPerEm', 'Units per Em'],
291
293
  ['ascent', 'Ascent'],
292
294
  ['descent', 'Descent'],
293
295
  ['lineGap', 'Line Gap'],
294
296
  ['xHeight', 'x-Height'],
295
297
  ['capHeight', 'Cap Height'],
296
- ].map(([key, label]) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }, children: [jsxRuntime.jsx("span", { style: { minWidth: 100, fontSize: 11 }, children: label }), jsxRuntime.jsx("input", { type: "number", value: editor.metrics[key], onChange: (e) => editor.setMetrics({ ...editor.metrics, [key]: Number(e.target.value) }), style: fieldStyle })] }, key))), jsxRuntime.jsxs("svg", { width: 300, height: 200, viewBox: `0 0 ${em} ${em}`, style: { background: 'var(--nice-bg, #141517)', borderRadius: 4, marginTop: 12 }, children: [jsxRuntime.jsx("rect", { x: 0, y: em - editor.metrics.ascent, width: em, height: editor.metrics.ascent - editor.metrics.descent, fill: "rgba(51,154,240,0.05)", stroke: "rgba(51,154,240,0.2)", strokeWidth: em * 0.003 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "var(--nice-info, #339af0)", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "var(--nice-success, #2f9e44)", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em + Math.abs(editor.metrics.descent), x2: em, y2: em + Math.abs(editor.metrics.descent), stroke: "#e03131", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "#f9e2af", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "#f38ba8", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsxRuntime.jsxs("text", { x: em * 0.02, y: em - editor.metrics.ascent + em * 0.05, fill: "var(--nice-info, #339af0)", fontSize: em * 0.04, children: ["Ascent (", editor.metrics.ascent, ")"] }), jsxRuntime.jsx("text", { x: em * 0.02, y: em + em * 0.05, fill: "var(--nice-success, #2f9e44)", fontSize: em * 0.04, children: "Baseline" }), jsxRuntime.jsxs("text", { x: em * 0.02, y: em - editor.metrics.xHeight + em * 0.05, fill: "#f9e2af", fontSize: em * 0.04, children: ["x-Height (", editor.metrics.xHeight, ")"] }), jsxRuntime.jsxs("text", { x: em * 0.02, y: em - editor.metrics.capHeight + em * 0.05, fill: "#f38ba8", fontSize: em * 0.04, children: ["Cap (", editor.metrics.capHeight, ")"] })] })] })), tab === 'kerning' && (jsxRuntime.jsxs("div", { style: { maxWidth: 500 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: "Kerning Pairs" }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, marginBottom: 8, alignItems: 'center', fontSize: 11 }, children: [jsxRuntime.jsx("input", { value: newKernLeft, onChange: (e) => setNewKernLeft(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsxRuntime.jsx("input", { value: newKernRight, onChange: (e) => setNewKernRight(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsxRuntime.jsx("button", { onClick: () => {
298
+ ].map(([key, label]) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }, children: [jsxRuntime.jsx("span", { style: { minWidth: 100, fontSize: 11 }, children: label }), jsxRuntime.jsx("input", { type: "number", value: editor.metrics[key], onChange: (e) => editor.setMetrics({ ...editor.metrics, [key]: Number(e.target.value) }), style: fieldStyle })] }, key))), jsxRuntime.jsxs("svg", { width: 300, height: 200, viewBox: `0 0 ${em} ${em}`, style: { background: 'var(--nice-bg, #141517)', borderRadius: 4, marginTop: 12 }, children: [jsxRuntime.jsx("rect", { x: 0, y: em - editor.metrics.ascent, width: em, height: editor.metrics.ascent - editor.metrics.descent, fill: "rgba(51,154,240,0.05)", stroke: "rgba(51,154,240,0.2)", strokeWidth: em * 0.003 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "var(--nice-info, #339af0)", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "var(--nice-success, #2f9e44)", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em + Math.abs(editor.metrics.descent), x2: em, y2: em + Math.abs(editor.metrics.descent), stroke: "#e03131", strokeWidth: em * 0.004 }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "#f9e2af", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsxRuntime.jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "#f38ba8", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsxRuntime.jsxs("text", { x: em * 0.02, y: em - editor.metrics.ascent + em * 0.05, fill: "var(--nice-info, #339af0)", fontSize: em * 0.04, children: ["Ascent (", editor.metrics.ascent, ")"] }), jsxRuntime.jsx("text", { x: em * 0.02, y: em + em * 0.05, fill: "var(--nice-success, #2f9e44)", fontSize: em * 0.04, children: t('fontEditor.baseline', 'Baseline') }), jsxRuntime.jsxs("text", { x: em * 0.02, y: em - editor.metrics.xHeight + em * 0.05, fill: "#f9e2af", fontSize: em * 0.04, children: ["x-Height (", editor.metrics.xHeight, ")"] }), jsxRuntime.jsxs("text", { x: em * 0.02, y: em - editor.metrics.capHeight + em * 0.05, fill: "#f38ba8", fontSize: em * 0.04, children: ["Cap (", editor.metrics.capHeight, ")"] })] })] })), tab === 'kerning' && (jsxRuntime.jsxs("div", { style: { maxWidth: 500 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: t('fontEditor.kerningPairs', 'Kerning Pairs') }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, marginBottom: 8, alignItems: 'center', fontSize: 11 }, children: [jsxRuntime.jsx("input", { value: newKernLeft, onChange: (e) => setNewKernLeft(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsxRuntime.jsx("input", { value: newKernRight, onChange: (e) => setNewKernRight(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsxRuntime.jsx("button", { onClick: () => {
297
299
  if (newKernLeft && newKernRight) {
298
300
  setKerningPairs((prev) => [
299
301
  ...prev,
300
302
  { left: newKernLeft, right: newKernRight, value: -50 },
301
303
  ]);
302
304
  }
303
- }, style: btnStyle(false), children: "Add Pair" })] }), jsxRuntime.jsx("div", { style: { maxHeight: 200, overflowY: 'auto' }, children: kerningPairs.length === 0 ? (jsxRuntime.jsx("div", { style: {
305
+ }, style: btnStyle(false), children: t('fontEditor.addPair', 'Add Pair') })] }), jsxRuntime.jsx("div", { style: { maxHeight: 200, overflowY: 'auto' }, children: kerningPairs.length === 0 ? (jsxRuntime.jsx("div", { style: {
304
306
  color: 'var(--nice-text-secondary, #868e96)',
305
307
  fontSize: 11,
306
308
  padding: 8,
307
309
  textAlign: 'center',
308
- }, children: "No kerning pairs defined" })) : (kerningPairs.map((kp, i) => (jsxRuntime.jsxs("div", { style: {
310
+ }, children: t('fontEditor.noKerningPairsDefined', 'No kerning pairs defined') })) : (kerningPairs.map((kp, i) => (jsxRuntime.jsxs("div", { style: {
309
311
  display: 'flex',
310
312
  gap: 8,
311
313
  alignItems: 'center',
@@ -316,7 +318,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
316
318
  border: 'none',
317
319
  color: 'var(--nice-danger, #ff6b6b)',
318
320
  cursor: 'pointer',
319
- }, "aria-label": "Remove kerning pair", title: "Remove kerning pair", children: jsxRuntime.jsx(uiCore.NiceIcon, { name: "close", size: 14 }) })] }, i)))) })] })), jsxRuntime.jsxs("div", { style: { marginTop: 8, borderTop: '1px solid var(--nice-border, #373a40)', paddingTop: 8 }, children: [jsxRuntime.jsx("div", { style: { fontSize: 11, color: 'var(--nice-text-secondary, #868e96)', marginBottom: 4 }, children: "Preview" }), jsxRuntime.jsx("input", { value: editor.previewText, onChange: (e) => editor.setPreviewText(e.target.value), style: { width: '100%', padding: '6px 8px', ...fieldStyle, fontSize: 12 } }), jsxRuntime.jsx("div", { style: {
321
+ }, "aria-label": t('fontEditor.removeKerningPair', 'Remove kerning pair'), title: t('fontEditor.removeKerningPair', 'Remove kerning pair'), children: jsxRuntime.jsx(uiCore.NiceIcon, { name: "close", size: 14 }) })] }, i)))) })] })), jsxRuntime.jsxs("div", { style: { marginTop: 8, borderTop: '1px solid var(--nice-border, #373a40)', paddingTop: 8 }, children: [jsxRuntime.jsx("div", { style: { fontSize: 11, color: 'var(--nice-text-secondary, #868e96)', marginBottom: 4 }, children: t('fontEditor.preview', 'Preview') }), jsxRuntime.jsx("input", { value: editor.previewText, onChange: (e) => editor.setPreviewText(e.target.value), style: { width: '100%', padding: '6px 8px', ...fieldStyle, fontSize: 12 } }), jsxRuntime.jsx("div", { style: {
320
322
  marginTop: 8,
321
323
  padding: 16,
322
324
  background: 'var(--nice-bg, #fff)',