@nice2dev/ui-graphics 1.2.0 → 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 (88) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/cjs/animation/Audience.js +3 -1
  3. package/dist/cjs/animation/rig/RigPlayer.js +1 -1
  4. package/dist/cjs/animation/rig/presets.js +67 -55
  5. package/dist/cjs/animation/shapes/headwear.js +4 -1
  6. package/dist/cjs/core/NiceBrushEngine.js +5 -3
  7. package/dist/cjs/core/editorAsk.js +48 -0
  8. package/dist/cjs/core/i18n.js +7 -3
  9. package/dist/cjs/core/plugins.js +1 -1
  10. package/dist/cjs/core/rtl.js +1 -1
  11. package/dist/cjs/core/theme.js +1 -1
  12. package/dist/cjs/font/NiceFontEditor.js +13 -11
  13. package/dist/cjs/game/GameAsset2dEditor.js +28 -16
  14. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  15. package/dist/cjs/photo/FilterThumb.js +3 -3
  16. package/dist/cjs/photo/NiceAITools.js +5 -3
  17. package/dist/cjs/photo/PhotoEditor.js +3 -1
  18. package/dist/cjs/photo/PhotoEditorCanvas.js +8 -6
  19. package/dist/cjs/photo/PhotoEditorLeftPanel.js +19 -19
  20. package/dist/cjs/photo/PhotoEditorToolbar.js +4 -4
  21. package/dist/cjs/photo/photoFilters.js +1 -0
  22. package/dist/cjs/pixel/PixelEditor.js +6 -3
  23. package/dist/cjs/pixel/PixelEditorCanvas.js +10 -6
  24. package/dist/cjs/pixel/PixelEditorMenuBar.js +3 -3
  25. package/dist/cjs/pixel/PixelEditorRightPanel.js +28 -24
  26. package/dist/cjs/pixel/PixelEditorStatusBar.js +8 -4
  27. package/dist/cjs/pixel/PixelEditorTimeline.js +11 -2
  28. package/dist/cjs/pixel/PixelEditorToolbar.js +10 -6
  29. package/dist/cjs/pixel/SpriteGeneratorPanel.js +11 -7
  30. package/dist/cjs/pixel/animalGeneratorPresets.js +31 -31
  31. package/dist/cjs/pixel/buildingGeneratorPresets.js +24 -24
  32. package/dist/cjs/pixel/characterGeneratorPresets.js +25 -25
  33. package/dist/cjs/pixel/usePixelEditor.js +13 -10
  34. package/dist/cjs/texture/Nice3DTexturePainter.js +10 -8
  35. package/dist/cjs/ui/NiceUIDesigner.js +17 -13
  36. package/dist/cjs/vector/NiceSvgToCode.js +6 -4
  37. package/dist/cjs/vector/VectorEditor.js +3 -2
  38. package/dist/cjs/vector/VectorEditorShapeRenderer.js +3 -1
  39. package/dist/cjs/vector/useVectorEditor.js +38 -29
  40. package/dist/cjs/vector/vectorEditorImport.js +1 -0
  41. package/dist/cjs/vector/vectorIconImport.js +1 -0
  42. package/dist/esm/animation/Audience.js +3 -1
  43. package/dist/esm/animation/rig/RigPlayer.js +1 -1
  44. package/dist/esm/animation/rig/presets.js +68 -56
  45. package/dist/esm/animation/shapes/headwear.js +4 -1
  46. package/dist/esm/animation/spineExport.js +1 -1
  47. package/dist/esm/core/NiceBrushEngine.js +5 -3
  48. package/dist/esm/core/editorAsk.js +45 -0
  49. package/dist/esm/core/i18n.js +8 -4
  50. package/dist/esm/core/plugins.js +1 -1
  51. package/dist/esm/core/rtl.js +1 -1
  52. package/dist/esm/core/theme.js +1 -1
  53. package/dist/esm/font/NiceFontEditor.js +13 -11
  54. package/dist/esm/game/GameAsset2dEditor.js +28 -16
  55. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  56. package/dist/esm/photo/FilterThumb.js +3 -3
  57. package/dist/esm/photo/NiceAITools.js +5 -3
  58. package/dist/esm/photo/PhotoEditor.js +3 -1
  59. package/dist/esm/photo/PhotoEditorCanvas.js +8 -6
  60. package/dist/esm/photo/PhotoEditorLeftPanel.js +19 -19
  61. package/dist/esm/photo/PhotoEditorToolbar.js +4 -4
  62. package/dist/esm/photo/photoFilters.js +1 -0
  63. package/dist/esm/pixel/PixelEditor.js +6 -3
  64. package/dist/esm/pixel/PixelEditorCanvas.js +10 -6
  65. package/dist/esm/pixel/PixelEditorMenuBar.js +3 -3
  66. package/dist/esm/pixel/PixelEditorRightPanel.js +28 -24
  67. package/dist/esm/pixel/PixelEditorStatusBar.js +8 -4
  68. package/dist/esm/pixel/PixelEditorTimeline.js +11 -2
  69. package/dist/esm/pixel/PixelEditorToolbar.js +10 -6
  70. package/dist/esm/pixel/SpriteGeneratorPanel.js +11 -7
  71. package/dist/esm/pixel/animalGeneratorPresets.js +32 -32
  72. package/dist/esm/pixel/buildingGeneratorPresets.js +25 -25
  73. package/dist/esm/pixel/characterGeneratorPresets.js +26 -26
  74. package/dist/esm/pixel/usePixelEditor.js +13 -10
  75. package/dist/esm/texture/Nice3DTexturePainter.js +10 -8
  76. package/dist/esm/ui/NiceUIDesigner.js +17 -13
  77. package/dist/esm/vector/NiceSvgToCode.js +6 -4
  78. package/dist/esm/vector/VectorEditor.js +3 -2
  79. package/dist/esm/vector/VectorEditorShapeRenderer.js +3 -1
  80. package/dist/esm/vector/useVectorEditor.js +38 -29
  81. package/dist/esm/vector/vectorEditorImport.js +1 -0
  82. package/dist/esm/vector/vectorIconImport.js +1 -0
  83. package/dist/types/core/LocalUI.d.ts +1 -2
  84. package/dist/types/core/editorAsk.d.ts +26 -0
  85. package/dist/types/core/i18n.d.ts +1 -17
  86. package/dist/types/pixel/usePixelEditor.d.ts +3 -0
  87. package/dist/types/vector/useVectorEditor.d.ts +2 -0
  88. package/package.json +5 -5
@@ -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 editorAsk = require('../core/editorAsk.js');
7
8
  var i18n = require('../core/i18n.js');
8
9
  var useVectorEditor = require('./useVectorEditor.js');
9
10
  var vectorAnimation = require('./vectorAnimation.js');
@@ -65,7 +66,7 @@ const NiceVectorEditor = ({ width: artW = 512, height: artH = 512, className, on
65
66
  }, [initialIcon, artW, artH, api]);
66
67
  const { shapes, selId, tool, zoom, px, py, dlgSvg, svgText, gridSize, boxRef, fInp, animations, previewTime, isPlaying, onDown, onMove, onUp, onWheel, onDblClick, openFile, switchTool, importSVGString, setDlgSvg, setSvgText, } = api;
67
68
  const toolRail = (jsxRuntime.jsx("div", { className: VectorEditor_module.toolbar, children: vectorEditorTypes.TOOLS.map((t) => (jsxRuntime.jsx(ui.NiceButton, { className: `${VectorEditor_module.toolBtn} ${tool === t.id ? VectorEditor_module.toolBtnActive : ''}`, onClick: () => switchTool(t.id), "aria-label": `${t.label} (${t.key})`, variant: tool === t.id ? 'primary' : 'ghost', size: "sm", children: jsxRuntime.jsx(uiCore.NiceIcon, { name: t.icon, size: "sm" }) }, t.id))) }));
68
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: fInp, type: "file", accept: ".svg", style: { display: 'none' }, onChange: (e) => {
69
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(editorAsk.EditorAskDialog, { ask: api.pendingAsk }), jsxRuntime.jsx("input", { ref: fInp, type: "file", accept: ".svg", style: { display: 'none' }, onChange: (e) => {
69
70
  var _a;
70
71
  const f = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
71
72
  if (f) {
@@ -111,7 +112,7 @@ const NiceVectorEditor = ({ width: artW = 512, height: artH = 512, className, on
111
112
  }
112
113
  const node = (jsxRuntime.jsx(VectorEditorShapeRenderer.ShapeRenderer, { shape: s, selected: s.id === selId, zoom: zoom }, s.id));
113
114
  return previewTr ? (jsxRuntime.jsx("g", { transform: previewTr, children: node }, s.id)) : (node);
114
- })] }) }) }), jsxRuntime.jsxs(ui.NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title: "Paste SVG", children: [jsxRuntime.jsx("label", { children: "Paste SVG source code below:" }), jsxRuntime.jsx(ui.NiceTextArea, { value: svgText, onChange: (val) => setSvgText(val), placeholder: '<svg xmlns="http://www.w3.org/2000/svg" ...>' }), jsxRuntime.jsxs("div", { className: VectorEditor_module.dialogActions, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", onClick: () => setDlgSvg(false), children: "Cancel" }), jsxRuntime.jsx(ui.NiceButton, { variant: "primary", onClick: () => importSVGString(svgText), children: "Import" })] })] }), jsxRuntime.jsxs(ui.NiceModal, { open: snippetOpen, onClose: () => setSnippetOpen(false), title: t('vectorEditor.snippet.title', 'Icon snippet copied to clipboard'), children: [jsxRuntime.jsxs("p", { style: { marginTop: 0 }, children: [t('vectorEditor.snippet.pasteHint', 'Paste into'), ' ', jsxRuntime.jsxs("code", { children: ["packages/icons/src/icons/", iconCategory ? `${iconCategory.toLowerCase()}` : '<category>', ".tsx"] }), ":"] }), jsxRuntime.jsx(ui.NiceTextArea, { value: snippetText, onChange: (val) => setSnippetText(val), rows: 16 }), jsxRuntime.jsxs("div", { className: VectorEditor_module.dialogActions, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "primary", onClick: () => {
115
+ })] }) }) }), jsxRuntime.jsxs(ui.NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title: t('vectorEditor.pasteSvg', 'Paste SVG'), children: [jsxRuntime.jsx("label", { children: "Paste SVG source code below:" }), jsxRuntime.jsx(ui.NiceTextArea, { value: svgText, onChange: (val) => setSvgText(val), placeholder: t('vectorEditor.svgXmlnsHttpWwwW3', '<svg xmlns="http://www.w3.org/2000/svg" ...>') }), jsxRuntime.jsxs("div", { className: VectorEditor_module.dialogActions, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", onClick: () => setDlgSvg(false), children: t('vectorEditor.cancel', 'Cancel') }), jsxRuntime.jsx(ui.NiceButton, { variant: "primary", onClick: () => importSVGString(svgText), children: t('vectorEditor.import', 'Import') })] })] }), jsxRuntime.jsxs(ui.NiceModal, { open: snippetOpen, onClose: () => setSnippetOpen(false), title: t('vectorEditor.snippet.title', 'Icon snippet copied to clipboard'), children: [jsxRuntime.jsxs("p", { style: { marginTop: 0 }, children: [t('vectorEditor.snippet.pasteHint', 'Paste into'), ' ', jsxRuntime.jsxs("code", { children: ["packages/icons/src/icons/", iconCategory ? `${iconCategory.toLowerCase()}` : '<category>', ".tsx"] }), ":"] }), jsxRuntime.jsx(ui.NiceTextArea, { value: snippetText, onChange: (val) => setSnippetText(val), rows: 16 }), jsxRuntime.jsxs("div", { className: VectorEditor_module.dialogActions, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "primary", onClick: () => {
115
116
  if (typeof navigator !== 'undefined' && navigator.clipboard) {
116
117
  navigator.clipboard.writeText(snippetText).catch(() => { });
117
118
  }
@@ -8,7 +8,9 @@ const ShapeRenderer = ({ shape: s, selected, zoom }) => {
8
8
  if (s.hidden) {
9
9
  return null;
10
10
  }
11
- const selStroke = selected ? 'rgba(85,102,204,0.6)' : undefined;
11
+ const selStroke = selected
12
+ ? 'color-mix(in srgb, var(--nice-color-primary, #5566cc) 60%, transparent)'
13
+ : undefined;
12
14
  const gProps = {};
13
15
  if (s.opacity < 1) {
14
16
  gProps.opacity = s.opacity;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var React = require('react');
4
+ var editorAsk = require('../core/editorAsk.js');
4
5
  var vectorBooleanOps = require('./vectorBooleanOps.js');
5
6
  var vectorEditorExport = require('./vectorEditorExport.js');
6
7
  var vectorEditorImport = require('./vectorEditorImport.js');
@@ -12,10 +13,13 @@ var vectorEditorTypes = require('./vectorEditorTypes.js');
12
13
  * so the component file is pure layout.
13
14
  */
14
15
  function useVectorEditor({ artW, artH, onSaveToLibrary }) {
16
+ // the library's dialogs for the text tool and the library name (not window.prompt)
17
+ const { pendingAsk, ask } = editorAsk.useEditorAsk();
15
18
  /* ── state ── */
16
19
  const [shapes, setShapes] = React.useState([]);
17
20
  const [selId, setSelId] = React.useState(null);
18
21
  const [tool, setTool] = React.useState('pointer');
22
+ // audit-allow: literal-color — the first fill of a new shape - document data
19
23
  const [fillCol, setFillCol] = React.useState('#5566cc');
20
24
  const [strokeCol, setStrokeCol] = React.useState('#000000');
21
25
  const [strokeW, setStrokeW] = React.useState(2);
@@ -178,28 +182,30 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
178
182
  return;
179
183
  }
180
184
  if (tool === 'text') {
181
- const txt = prompt('Enter text:', 'Hello');
182
- if (!txt) {
183
- return;
184
- }
185
- const ns = {
186
- id: vectorEditorTypes.sid(),
187
- type: 'text',
188
- x: p.x,
189
- y: p.y,
190
- w: txt.length * fontSize * 0.6,
191
- h: fontSize,
192
- ...defaultBase,
193
- stroke: 'none',
194
- strokeWidth: 0,
195
- text: txt,
196
- fontSize,
197
- fontFamily,
198
- };
199
- const next = [...shapes, ns];
200
- snap(next);
201
- setShapes(next);
202
- setSelId(ns.id);
185
+ // the text comes from a dialog: the shape is added once it is entered
186
+ void ask('prompt', 'Enter text:', 'Hello').then((txt) => {
187
+ if (!txt) {
188
+ return;
189
+ }
190
+ const ns = {
191
+ id: vectorEditorTypes.sid(),
192
+ type: 'text',
193
+ x: p.x,
194
+ y: p.y,
195
+ w: txt.length * fontSize * 0.6,
196
+ h: fontSize,
197
+ ...defaultBase,
198
+ stroke: 'none',
199
+ strokeWidth: 0,
200
+ text: txt,
201
+ fontSize,
202
+ fontFamily,
203
+ };
204
+ const next = [...shapes, ns];
205
+ snap(next);
206
+ setShapes(next);
207
+ setSelId(ns.id);
208
+ });
203
209
  return;
204
210
  }
205
211
  if (tool === 'polygon') {
@@ -285,6 +291,7 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
285
291
  starPoints2,
286
292
  starInnerRatio,
287
293
  cornerRadius,
294
+ ask,
288
295
  ]);
289
296
  const onMove = React.useCallback((e) => {
290
297
  if (panning.current) {
@@ -699,15 +706,15 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
699
706
  const blob = new Blob([svg], { type: 'image/svg+xml' });
700
707
  const reader = new FileReader();
701
708
  reader.onload = () => {
702
- const defaultName = 'drawing.svg';
703
- const name = window.prompt('Save to library as:', defaultName);
704
- if (!name || !name.trim()) {
705
- return;
706
- }
707
- onSaveToLibrary(reader.result, name.trim(), 'image/svg+xml');
709
+ const dataUrl = reader.result;
710
+ void ask('prompt', 'Save to library as:', 'drawing.svg').then((name) => {
711
+ if (name && name.trim()) {
712
+ onSaveToLibrary(dataUrl, name.trim(), 'image/svg+xml');
713
+ }
714
+ });
708
715
  };
709
716
  reader.readAsDataURL(blob);
710
- }, [shapes, artW, artH, onSaveToLibrary]);
717
+ }, [shapes, artW, artH, onSaveToLibrary, ask]);
711
718
  /* ── export shortcuts ── */
712
719
  const exportSVG = React.useCallback(() => vectorEditorExport.exportSVGFile(shapes, artW, artH, animations), [shapes, artW, artH, animations]);
713
720
  const exportPNG = React.useCallback(() => vectorEditorExport.exportPNGFile(shapes, artW, artH), [shapes, artW, artH]);
@@ -793,6 +800,8 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
793
800
  /* ── return ── */
794
801
  return {
795
802
  // state
803
+ /** The question the editor waits on; render it with `EditorAskDialog`. */
804
+ pendingAsk,
796
805
  shapes,
797
806
  selId,
798
807
  tool,
@@ -18,6 +18,7 @@ function parseSVGToShapes(raw) {
18
18
  const elems = doc.querySelectorAll('rect, ellipse, circle, line, polyline, polygon, text, path');
19
19
  elems.forEach((el) => {
20
20
  const tag = el.tagName.toLowerCase();
21
+ // audit-allow: literal-color — an imported shape without a fill gets a visible default - document data
21
22
  const fill = el.getAttribute('fill') || (tag === 'line' ? 'none' : '#cccccc');
22
23
  const stroke = el.getAttribute('stroke') || 'none';
23
24
  const sw = parseFloat(el.getAttribute('stroke-width') || '1');
@@ -39,6 +39,7 @@ function iconComponentToShapes(Icon, options) {
39
39
  // Force a generous size and visible colors so the parser sees concrete strokes/fills.
40
40
  const renderProps = {
41
41
  size: 'lg',
42
+ // audit-allow: literal-color +2 — an imported icon's default colours - document data
42
43
  primaryColor: '#5566cc',
43
44
  secondaryColor: '#a29bfe',
44
45
  strokeWidth: 2,
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { NiceButton } from '@nice2dev/ui';
3
3
  import React from 'react';
4
+ import { useNiceTranslation } from '../core/i18n.js';
4
5
  import NiceAnimatedPerson from './AnimatedPerson.js';
5
6
  import { DEFAULT_CHARACTER } from './characterTypes.js';
6
7
  import { runRounds, seq } from './choreoDSL.js';
@@ -33,6 +34,7 @@ function makeAudience(n) {
33
34
  }
34
35
  /** Grid of animated audience members with wave/cheer actions. */
35
36
  const NiceAudience = ({ count = 36, columns = 12 }) => {
37
+ const { t } = useNiceTranslation();
36
38
  const people = React.useMemo(() => makeAudience(count), [count]);
37
39
  const refs = React.useRef([]);
38
40
  refs.current = [];
@@ -57,7 +59,7 @@ const NiceAudience = ({ count = 36, columns = 12 }) => {
57
59
  display: 'grid',
58
60
  gap: 8,
59
61
  gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
60
- }, children: people.map((c, i) => (jsx(NiceAnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsx(NiceButton, { variant: "ghost", size: "sm", onClick: wave, children: "Wave" }), jsx(NiceButton, { variant: "ghost", size: "sm", onClick: cheer, children: "Cheer" })] })] }));
62
+ }, children: people.map((c, i) => (jsx(NiceAnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsx(NiceButton, { variant: "ghost", size: "sm", onClick: wave, children: t('audience.wave', 'Wave') }), jsx(NiceButton, { variant: "ghost", size: "sm", onClick: cheer, children: t('audience.cheer', 'Cheer') })] })] }));
61
63
  };
62
64
 
63
65
  export { NiceAudience as default };
@@ -7,7 +7,7 @@ import NiceRiggedBody from './RiggedBody.js';
7
7
  import { solveFK } from './rigMath.js';
8
8
  import { DEFAULT_POSE, DEFAULT_RIG } from './rigTypes.js';
9
9
 
10
- const NiceRigPlayer = React.forwardRef(({ character, rig, pose, size, debug }, ref) => {
10
+ const NiceRigPlayer = /* @__PURE__ */ React.forwardRef(({ character, rig, pose, size, debug }, ref) => {
11
11
  var _a;
12
12
  const [current, setCurrent] = React.useState({ ...DEFAULT_POSE, ...(pose !== null && pose !== void 0 ? pose : {}) });
13
13
  const r = rig !== null && rig !== void 0 ? rig : DEFAULT_RIG;
@@ -1,65 +1,77 @@
1
- import { DEFAULT_RIG, DEFAULT_POSE } from './rigTypes.js';
1
+ import { DEFAULT_POSE, DEFAULT_RIG } from './rigTypes.js';
2
2
 
3
3
  /** Slim proportions preset. */
4
- const RIG_SLIM = {
5
- ...DEFAULT_RIG,
6
- lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.11 },
7
- lForearm: { ...DEFAULT_RIG.lForearm, length: 0.11 },
8
- rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.11 },
9
- rForearm: { ...DEFAULT_RIG.rForearm, length: 0.11 },
10
- lThigh: { ...DEFAULT_RIG.lThigh, length: 0.14 },
11
- lShin: { ...DEFAULT_RIG.lShin, length: 0.14 },
12
- rThigh: { ...DEFAULT_RIG.rThigh, length: 0.14 },
13
- rShin: { ...DEFAULT_RIG.rShin, length: 0.14 },
14
- };
4
+ const RIG_SLIM = /* @__PURE__ */ (function () {
5
+ return {
6
+ ...DEFAULT_RIG,
7
+ lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.11 },
8
+ lForearm: { ...DEFAULT_RIG.lForearm, length: 0.11 },
9
+ rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.11 },
10
+ rForearm: { ...DEFAULT_RIG.rForearm, length: 0.11 },
11
+ lThigh: { ...DEFAULT_RIG.lThigh, length: 0.14 },
12
+ lShin: { ...DEFAULT_RIG.lShin, length: 0.14 },
13
+ rThigh: { ...DEFAULT_RIG.rThigh, length: 0.14 },
14
+ rShin: { ...DEFAULT_RIG.rShin, length: 0.14 },
15
+ };
16
+ })();
15
17
  /** Broad shoulders preset. */
16
- const RIG_HERO = {
17
- ...DEFAULT_RIG,
18
- lShoulder: { ...DEFAULT_RIG.lShoulder, offset: [-0.13, 0] },
19
- rShoulder: { ...DEFAULT_RIG.rShoulder, offset: [0.13, 0] },
20
- lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.13 },
21
- rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.13 },
22
- };
18
+ const RIG_HERO = /* @__PURE__ */ (function () {
19
+ return {
20
+ ...DEFAULT_RIG,
21
+ lShoulder: { ...DEFAULT_RIG.lShoulder, offset: [-0.13, 0] },
22
+ rShoulder: { ...DEFAULT_RIG.rShoulder, offset: [0.13, 0] },
23
+ lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.13 },
24
+ rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.13 },
25
+ };
26
+ })();
23
27
  /** Robot-like short limbs preset. */
24
- const RIG_ROBOT = {
25
- ...DEFAULT_RIG,
26
- lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.1 },
27
- lForearm: { ...DEFAULT_RIG.lForearm, length: 0.1 },
28
- rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.1 },
29
- rForearm: { ...DEFAULT_RIG.rForearm, length: 0.1 },
30
- lThigh: { ...DEFAULT_RIG.lThigh, length: 0.13 },
31
- lShin: { ...DEFAULT_RIG.lShin, length: 0.13 },
32
- rThigh: { ...DEFAULT_RIG.rThigh, length: 0.13 },
33
- rShin: { ...DEFAULT_RIG.rShin, length: 0.13 },
34
- };
28
+ const RIG_ROBOT = /* @__PURE__ */ (function () {
29
+ return {
30
+ ...DEFAULT_RIG,
31
+ lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.1 },
32
+ lForearm: { ...DEFAULT_RIG.lForearm, length: 0.1 },
33
+ rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.1 },
34
+ rForearm: { ...DEFAULT_RIG.rForearm, length: 0.1 },
35
+ lThigh: { ...DEFAULT_RIG.lThigh, length: 0.13 },
36
+ lShin: { ...DEFAULT_RIG.lShin, length: 0.13 },
37
+ rThigh: { ...DEFAULT_RIG.rThigh, length: 0.13 },
38
+ rShin: { ...DEFAULT_RIG.rShin, length: 0.13 },
39
+ };
40
+ })();
35
41
  /** Happy pose. */
36
- const POSE_HAPPY = {
37
- ...DEFAULT_POSE,
38
- spineUpper: 80,
39
- neck: 70,
40
- head: 60,
41
- lUpperArm: 60,
42
- lForearm: 20,
43
- rUpperArm: 120,
44
- rForearm: 160,
45
- };
42
+ const POSE_HAPPY = /* @__PURE__ */ (function () {
43
+ return {
44
+ ...DEFAULT_POSE,
45
+ spineUpper: 80,
46
+ neck: 70,
47
+ head: 60,
48
+ lUpperArm: 60,
49
+ lForearm: 20,
50
+ rUpperArm: 120,
51
+ rForearm: 160,
52
+ };
53
+ })();
46
54
  /** Angry pose. */
47
- const POSE_ANGRY = {
48
- ...DEFAULT_POSE,
49
- head: 90,
50
- lUpperArm: 150,
51
- rUpperArm: 30,
52
- lForearm: 160,
53
- rForearm: 20,
54
- };
55
+ const POSE_ANGRY = /* @__PURE__ */ (function () {
56
+ return {
57
+ ...DEFAULT_POSE,
58
+ head: 90,
59
+ lUpperArm: 150,
60
+ rUpperArm: 30,
61
+ lForearm: 160,
62
+ rForearm: 20,
63
+ };
64
+ })();
55
65
  /** Dance pose A. */
56
- const POSE_DANCE_A = {
57
- ...DEFAULT_POSE,
58
- pelvis: 100,
59
- lThigh: 120,
60
- rThigh: 70,
61
- lUpperArm: 30,
62
- rUpperArm: 150,
63
- };
66
+ const POSE_DANCE_A = /* @__PURE__ */ (function () {
67
+ return {
68
+ ...DEFAULT_POSE,
69
+ pelvis: 100,
70
+ lThigh: 120,
71
+ rThigh: 70,
72
+ lUpperArm: 30,
73
+ rUpperArm: 150,
74
+ };
75
+ })();
64
76
 
65
77
  export { POSE_ANGRY, POSE_DANCE_A, POSE_HAPPY, RIG_HERO, RIG_ROBOT, RIG_SLIM };
@@ -8,13 +8,16 @@ const HeadwearCap = ({ colors, scale = 1, transform }) => {
8
8
  };
9
9
  const HeadwearCrown = ({ colors, scale = 1, transform }) => {
10
10
  const base = pick(colors, 0, 'var(--nice-warning, #FFD700)');
11
- return (jsx("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: 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 }) }));
11
+ return (jsx("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: jsx("path", { d: "M-22 -30 l6 -16 l8 10 l8 -14 l8 14 l8 -10 l6 16 z", fill: base,
12
+ // audit-allow: literal-color — a character-generator shape: its own drawing colour
13
+ stroke: "#B8860B", strokeWidth: 1.2 }) }));
12
14
  };
13
15
  const HeadwearHeadphones = ({ colors, scale = 1, transform }) => {
14
16
  const base = pick(colors, 0, 'var(--nice-text, #333)');
15
17
  return (jsxs("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, stroke: base, strokeWidth: 2.5, fill: "none", children: [jsx("path", { d: "M-30 0 q0 -40 30 -42 q30 2 30 42" }), jsx("rect", { x: -36, y: -6, width: 10, height: 16, rx: 3, fill: base }), jsx("rect", { x: 26, y: -6, width: 10, height: 16, rx: 3, fill: base })] }));
16
18
  };
17
19
  const HeadwearBeanie = ({ colors, scale = 1, transform }) => {
20
+ // audit-allow: literal-color — the hat's default colour when the character sets none
18
21
  const base = pick(colors, 0, '#E53E3E');
19
22
  return (jsxs("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: [jsx("path", { d: "M-28 -14 q0 -22 28 -26 q28 4 28 26 z", fill: base }), jsx("circle", { cx: 0, cy: -42, r: 6, fill: base })] }));
20
23
  };
@@ -1,5 +1,5 @@
1
1
  import { solveFK } from './rig/rigMath.js';
2
- import { DEFAULT_RIG, DEFAULT_POSE, BONE_ORDER } from './rig/rigTypes.js';
2
+ import { DEFAULT_RIG, BONE_ORDER, DEFAULT_POSE } from './rig/rigTypes.js';
3
3
 
4
4
  /**
5
5
  * spineExport.ts — Export animations to Spine JSON format.
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { NiceIcon } from '@nice2dev/ui-core';
3
3
  import { useEffect, useState, useRef, useCallback } from 'react';
4
+ import { useNiceTranslation } from './i18n.js';
4
5
 
5
6
  /* ── Brush Presets ── */
6
7
  const DEFAULT_DYNAMICS = {
@@ -541,6 +542,7 @@ color = '#000000') {
541
542
  }
542
543
  /* ── Component ── */
543
544
  const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '', }) => {
545
+ const { t } = useNiceTranslation();
544
546
  const engine = useBrushEngine(canvas, color);
545
547
  // Override endStroke to fire callback
546
548
  useEffect(() => {
@@ -557,7 +559,7 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
557
559
  return () => canvas.removeEventListener('pointerup', onUp);
558
560
  }, [canvas, engine.endStroke, onStrokeEnd]);
559
561
  const s = engine.settings;
560
- return (jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "brush", size: 14 }), " Brush Engine v2"] }), jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: engine.presetNames.map((name) => {
562
+ return (jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "brush", size: 14 }), " ", t('brushEngine.brushEngineV2', 'Brush Engine v2')] }), jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: engine.presetNames.map((name) => {
561
563
  var _a, _b;
562
564
  return (jsx("button", { type: "button", onClick: () => engine.applyPreset(name), style: {
563
565
  padding: '2px 6px',
@@ -570,13 +572,13 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
570
572
  : 'transparent',
571
573
  color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? 'var(--nice-color-white, #fff)' : 'inherit',
572
574
  }, children: BRUSH_PRESETS[name].name }, name));
573
- }) }), jsxs("label", { children: ["Size: ", s.size, "px", jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Opacity: ", Math.round(s.opacity * 100), "%", 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%' } })] }), jsxs("label", { children: ["Flow: ", Math.round(s.flow * 100), "%", 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%' } })] }), jsxs("label", { children: ["Hardness: ", Math.round(s.hardness * 100), "%", 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%' } })] }), jsxs("label", { children: ["Smoothing: ", Math.round(s.smoothing * 100), "%", 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%' } })] }), jsxs("label", { children: ["Tip:", jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsx("option", { value: "circle", children: "Circle" }), jsx("option", { value: "square", children: "Square" }), jsx("option", { value: "diamond", children: "Diamond" }), jsx("option", { value: "texture", children: "Texture" })] })] }), jsxs("label", { children: ["Roundness: ", Math.round(s.roundness * 100), "%", 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%' } })] }), jsxs("details", { children: [jsxs("summary", { style: { cursor: 'pointer', fontSize: 12 }, children: [jsx(NiceIcon, { name: "bar-chart", size: 12 }), " Size Dynamics"] }), jsxs("label", { children: ["Source:", jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
575
+ }) }), jsxs("label", { children: ["Size: ", s.size, "px", jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Opacity: ", Math.round(s.opacity * 100), "%", 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%' } })] }), jsxs("label", { children: ["Flow: ", Math.round(s.flow * 100), "%", 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%' } })] }), jsxs("label", { children: ["Hardness: ", Math.round(s.hardness * 100), "%", 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%' } })] }), jsxs("label", { children: ["Smoothing: ", Math.round(s.smoothing * 100), "%", 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%' } })] }), jsxs("label", { children: ["Tip:", jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsx("option", { value: "circle", children: t('brushEngine.circle', 'Circle') }), jsx("option", { value: "square", children: t('brushEngine.square', 'Square') }), jsx("option", { value: "diamond", children: t('brushEngine.diamond', 'Diamond') }), jsx("option", { value: "texture", children: t('brushEngine.texture', 'Texture') })] })] }), jsxs("label", { children: ["Roundness: ", Math.round(s.roundness * 100), "%", 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%' } })] }), jsxs("details", { children: [jsxs("summary", { style: { cursor: 'pointer', fontSize: 12 }, children: [jsx(NiceIcon, { name: "bar-chart", size: 12 }), " ", t('brushEngine.sizeDynamics', 'Size Dynamics')] }), jsxs("label", { children: ["Source:", jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
574
576
  ...s,
575
577
  sizeDynamics: {
576
578
  ...s.sizeDynamics,
577
579
  source: e.target.value,
578
580
  },
579
- }), style: { width: '100%' }, children: [jsx("option", { value: "none", children: "Off" }), jsx("option", { value: "pressure", children: "Pressure" }), jsx("option", { value: "tilt", children: "Tilt" }), jsx("option", { value: "speed", children: "Speed" }), jsx("option", { value: "random", children: "Random" })] })] })] }), jsxs("label", { children: ["Scatter: ", Math.round(s.scatter * 100), "%", 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%' } })] }), jsxs("label", { children: ["Blend:", 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) => (jsx("option", { value: m, children: m }, m))) })] }), jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsx("input", { type: "checkbox", checked: s.wetEdges, onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked }) }), "Wet Edges"] })] }));
581
+ }), style: { width: '100%' }, children: [jsx("option", { value: "none", children: "Off" }), jsx("option", { value: "pressure", children: t('brushEngine.pressure', 'Pressure') }), jsx("option", { value: "tilt", children: t('brushEngine.tilt', 'Tilt') }), jsx("option", { value: "speed", children: t('brushEngine.speed', 'Speed') }), jsx("option", { value: "random", children: t('brushEngine.random', 'Random') })] })] })] }), jsxs("label", { children: ["Scatter: ", Math.round(s.scatter * 100), "%", 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%' } })] }), jsxs("label", { children: ["Blend:", 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) => (jsx("option", { value: m, children: m }, m))) })] }), jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsx("input", { type: "checkbox", checked: s.wetEdges, onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked }) }), t('brushEngine.wetEdges', 'Wet Edges')] })] }));
580
582
  };
581
583
 
582
584
  export { BRUSH_PRESETS, NiceBrushEngine, useBrushEngine };
@@ -0,0 +1,45 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { NiceConfirmDialog, NicePromptDialog } from '@nice2dev/ui';
3
+ import { useState, useRef, useCallback } from 'react';
4
+
5
+ /**
6
+ * The library's dialogs in place of `window.prompt` / `window.confirm` for the editor hooks,
7
+ * which hold state but render nothing: `ask` resolves with the answer (`null` when cancelled)
8
+ * and `pendingAsk` is the open question, for the editor component to pass to
9
+ * {@link EditorAskDialog}. One question at a time: a new one settles the open one as cancelled.
10
+ */
11
+ function useEditorAsk() {
12
+ const [pendingAsk, setPendingAsk] = useState(null);
13
+ const pendingRef = useRef(null);
14
+ const ask = useCallback((kind, text, defaultValue) => new Promise((resolve) => {
15
+ var _a;
16
+ (_a = pendingRef.current) === null || _a === void 0 ? void 0 : _a.answer(null);
17
+ const next = {
18
+ kind,
19
+ text,
20
+ defaultValue,
21
+ answer: (value) => {
22
+ if (pendingRef.current === next) {
23
+ pendingRef.current = null;
24
+ setPendingAsk(null);
25
+ }
26
+ resolve(value);
27
+ },
28
+ };
29
+ pendingRef.current = next;
30
+ setPendingAsk(next);
31
+ }), []);
32
+ return { pendingAsk, ask };
33
+ }
34
+ /** Shows the question {@link useEditorAsk} is waiting on, if any. */
35
+ const EditorAskDialog = ({ ask }) => {
36
+ if (!ask) {
37
+ return null;
38
+ }
39
+ if (ask.kind === 'confirm') {
40
+ return (jsx(NiceConfirmDialog, { visible: true, message: ask.text, onAccept: () => ask.answer(''), onReject: () => ask.answer(null) }));
41
+ }
42
+ return (jsx(NicePromptDialog, { open: true, title: ask.text, defaultValue: ask.defaultValue, onSubmit: (value) => ask.answer(value), onCancel: () => ask.answer(null) }));
43
+ };
44
+
45
+ export { EditorAskDialog, useEditorAsk };
@@ -1,11 +1,15 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { createContext, useContext } from 'react';
2
+ import { useNiceTranslation as useNiceTranslation$1 } from '@nice2dev/ui-core';
3
+ import { useContext, useMemo, createContext } from 'react';
3
4
 
4
- const fallback = (_key, defaultValue) => defaultValue;
5
- const NiceI18nContext = createContext(fallback);
5
+ /** `null` outside this package's provider: the global translator applies. */
6
+ const NiceI18nContext = /* @__PURE__ */ createContext(null);
7
+ /** Overrides the app's global translator for the graphics controls below it. */
6
8
  const NiceI18nProvider = ({ t, children }) => (jsx(NiceI18nContext.Provider, { value: t, children: children }));
7
9
  function useNiceTranslation() {
8
- const t = useContext(NiceI18nContext);
10
+ const own = useContext(NiceI18nContext);
11
+ const { t: global } = useNiceTranslation$1();
12
+ const t = useMemo(() => own !== null && own !== void 0 ? own : ((key, defaultValue) => global(key, defaultValue)), [own, global]);
9
13
  return { t };
10
14
  }
11
15
 
@@ -110,7 +110,7 @@ function usePlugins(editor) {
110
110
  };
111
111
  }
112
112
  // ── Plugin Context (React) ──────────────────────────────────────
113
- const PluginContext = React.createContext(PluginRegistry);
113
+ const PluginContext = /* @__PURE__ */ React.createContext(PluginRegistry);
114
114
  const NicePluginProvider = ({ plugins, children }) => {
115
115
  React.useEffect(() => {
116
116
  if (plugins) {
@@ -12,7 +12,7 @@ const defaultRTLConfig = {
12
12
  mirrorToolbar: false,
13
13
  mirrorPanels: false,
14
14
  };
15
- const RTLContext = React.createContext(defaultRTLConfig);
15
+ const RTLContext = /* @__PURE__ */ React.createContext(defaultRTLConfig);
16
16
  /** Provider for RTL configuration. Wraps editors to enable RTL support. */
17
17
  const NiceRTLProvider = ({ direction = 'ltr', mirrorToolbar = true, mirrorPanels = true, children }) => {
18
18
  const config = React.useMemo(() => ({
@@ -107,7 +107,7 @@ function resolveThemeMode(mode) {
107
107
  }
108
108
  return mode;
109
109
  }
110
- const ThemeContext = React.createContext({
110
+ const ThemeContext = /* @__PURE__ */ React.createContext({
111
111
  theme: DARK_THEME,
112
112
  mode: 'dark',
113
113
  setMode: () => { },