@nice2dev/ui-graphics 1.2.0 → 1.2.2

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 (98) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +3 -1
  3. package/dist/cjs/animation/Audience.js +3 -1
  4. package/dist/cjs/animation/rig/RigPlayer.js +1 -1
  5. package/dist/cjs/animation/rig/presets.js +67 -55
  6. package/dist/cjs/animation/shapes/headwear.js +4 -1
  7. package/dist/cjs/core/NiceBrushEngine.js +8 -5
  8. package/dist/cjs/core/collaboration.js +11 -6
  9. package/dist/cjs/core/editorAsk.js +48 -0
  10. package/dist/cjs/core/i18n.js +7 -3
  11. package/dist/cjs/core/minimap.js +21 -20
  12. package/dist/cjs/core/plugins.js +1 -1
  13. package/dist/cjs/core/rtl.js +1 -1
  14. package/dist/cjs/core/theme.js +1 -1
  15. package/dist/cjs/font/NiceFontEditor.js +14 -12
  16. package/dist/cjs/game/GameAsset2dEditor.js +75 -44
  17. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  18. package/dist/cjs/photo/FilterThumb.js +3 -3
  19. package/dist/cjs/photo/NiceAITools.js +6 -4
  20. package/dist/cjs/photo/PhotoEditor.js +3 -1
  21. package/dist/cjs/photo/PhotoEditorCanvas.js +8 -6
  22. package/dist/cjs/photo/PhotoEditorLeftPanel.js +29 -29
  23. package/dist/cjs/photo/PhotoEditorToolbar.js +4 -4
  24. package/dist/cjs/photo/photoFilters.js +1 -0
  25. package/dist/cjs/pixel/PixelEditor.js +6 -3
  26. package/dist/cjs/pixel/PixelEditorCanvas.js +10 -6
  27. package/dist/cjs/pixel/PixelEditorMenuBar.js +95 -30
  28. package/dist/cjs/pixel/PixelEditorRightPanel.js +32 -24
  29. package/dist/cjs/pixel/PixelEditorStatusBar.js +12 -4
  30. package/dist/cjs/pixel/PixelEditorTimeline.js +15 -2
  31. package/dist/cjs/pixel/PixelEditorToolbar.js +12 -6
  32. package/dist/cjs/pixel/SpriteGeneratorPanel.js +28 -17
  33. package/dist/cjs/pixel/animalGeneratorPresets.js +31 -31
  34. package/dist/cjs/pixel/buildingGeneratorPresets.js +24 -24
  35. package/dist/cjs/pixel/characterGeneratorPresets.js +25 -25
  36. package/dist/cjs/pixel/usePixelEditor.js +13 -10
  37. package/dist/cjs/texture/Nice3DTexturePainter.js +16 -10
  38. package/dist/cjs/ui/NiceUIDesigner.js +17 -13
  39. package/dist/cjs/vector/NiceSvgToCode.js +7 -5
  40. package/dist/cjs/vector/VectorEditor.js +3 -2
  41. package/dist/cjs/vector/VectorEditorMenuBar.js +7 -1
  42. package/dist/cjs/vector/VectorEditorRightPanel.js +1 -1
  43. package/dist/cjs/vector/VectorEditorShapeRenderer.js +3 -1
  44. package/dist/cjs/vector/useVectorEditor.js +38 -29
  45. package/dist/cjs/vector/vectorEditorImport.js +1 -0
  46. package/dist/cjs/vector/vectorIconImport.js +1 -0
  47. package/dist/esm/animation/AnimatedPerson.js +3 -1
  48. package/dist/esm/animation/Audience.js +3 -1
  49. package/dist/esm/animation/rig/RigPlayer.js +1 -1
  50. package/dist/esm/animation/rig/presets.js +68 -56
  51. package/dist/esm/animation/shapes/headwear.js +4 -1
  52. package/dist/esm/animation/spineExport.js +1 -1
  53. package/dist/esm/core/NiceBrushEngine.js +8 -5
  54. package/dist/esm/core/collaboration.js +11 -6
  55. package/dist/esm/core/editorAsk.js +45 -0
  56. package/dist/esm/core/i18n.js +8 -4
  57. package/dist/esm/core/minimap.js +21 -20
  58. package/dist/esm/core/plugins.js +1 -1
  59. package/dist/esm/core/rtl.js +1 -1
  60. package/dist/esm/core/theme.js +1 -1
  61. package/dist/esm/font/NiceFontEditor.js +14 -12
  62. package/dist/esm/game/GameAsset2dEditor.js +75 -44
  63. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  64. package/dist/esm/photo/FilterThumb.js +3 -3
  65. package/dist/esm/photo/NiceAITools.js +6 -4
  66. package/dist/esm/photo/PhotoEditor.js +3 -1
  67. package/dist/esm/photo/PhotoEditorCanvas.js +8 -6
  68. package/dist/esm/photo/PhotoEditorLeftPanel.js +29 -29
  69. package/dist/esm/photo/PhotoEditorToolbar.js +4 -4
  70. package/dist/esm/photo/photoFilters.js +1 -0
  71. package/dist/esm/pixel/PixelEditor.js +6 -3
  72. package/dist/esm/pixel/PixelEditorCanvas.js +10 -6
  73. package/dist/esm/pixel/PixelEditorMenuBar.js +95 -30
  74. package/dist/esm/pixel/PixelEditorRightPanel.js +32 -24
  75. package/dist/esm/pixel/PixelEditorStatusBar.js +12 -4
  76. package/dist/esm/pixel/PixelEditorTimeline.js +15 -2
  77. package/dist/esm/pixel/PixelEditorToolbar.js +12 -6
  78. package/dist/esm/pixel/SpriteGeneratorPanel.js +28 -17
  79. package/dist/esm/pixel/animalGeneratorPresets.js +32 -32
  80. package/dist/esm/pixel/buildingGeneratorPresets.js +25 -25
  81. package/dist/esm/pixel/characterGeneratorPresets.js +26 -26
  82. package/dist/esm/pixel/usePixelEditor.js +13 -10
  83. package/dist/esm/texture/Nice3DTexturePainter.js +16 -10
  84. package/dist/esm/ui/NiceUIDesigner.js +17 -13
  85. package/dist/esm/vector/NiceSvgToCode.js +7 -5
  86. package/dist/esm/vector/VectorEditor.js +3 -2
  87. package/dist/esm/vector/VectorEditorMenuBar.js +7 -1
  88. package/dist/esm/vector/VectorEditorRightPanel.js +1 -1
  89. package/dist/esm/vector/VectorEditorShapeRenderer.js +3 -1
  90. package/dist/esm/vector/useVectorEditor.js +38 -29
  91. package/dist/esm/vector/vectorEditorImport.js +1 -0
  92. package/dist/esm/vector/vectorIconImport.js +1 -0
  93. package/dist/types/core/LocalUI.d.ts +1 -2
  94. package/dist/types/core/editorAsk.d.ts +26 -0
  95. package/dist/types/core/i18n.d.ts +1 -17
  96. package/dist/types/pixel/usePixelEditor.d.ts +3 -0
  97. package/dist/types/vector/useVectorEditor.d.ts +2 -0
  98. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,147 @@
1
1
  # @nice2dev/ui-graphics — Changelog
2
2
 
3
+ ## 1.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 06eb0d2: 1.2.2 — library texts through the translator, a translator in the packages that had none, and the
8
+ end of the hook-dependency warnings. Nothing removed or renamed. Upgrade notes:
9
+ `documentation/omniverk/OMNIVERK_UPGRADE_1.2.2.md`.
10
+
11
+ **Translations.** 2,825 new keys in the global dictionary (en, pl, de, fr, es, uk), 80 in the local
12
+ dictionaries of ui-math, ui-diagrams and ui-presentations: column headers, option labels, texts in
13
+ conditions and templates, messages given to setters, labels returned by helpers, Polish texts that
14
+ were the English default. Relative dates through `Intl.RelativeTimeFormat`, plural forms through
15
+ `…One` / `…Many` keys. ui-science, ui-platform, ui-mobile, auth, licensing, templates, social and
16
+ the icon tools translate through `useNiceTranslation` (new peer `@nice2dev/ui-core ^1.2.0`);
17
+ ui-security reads the global translator when its own provider is absent.
18
+
19
+ **Additions.** `labelKey` on ui-mobile `TabItem` and `SwipeAction` (the presets set it);
20
+ `registerWebAuthn` / `authenticateWebAuthn` take the WebAuthn options `NiceWebAuthnButton` used to
21
+ drop (defaults unchanged); `NiceKeyboardHint` shows `descriptionKey`. Eight icons in
22
+ `@nice2dev/icons` — `NtdPoint`, `NtdSegment`, `NtdPerpendicular`, `NtdParallel`, `NtdMidpoint`,
23
+ `NtdAngleBisector`, `NtdGroup`, `NtdUngroup` — and the same names as `NiceIcon` built-ins.
24
+
25
+ **Behaviour changes, agreed.** ui-math, ui-diagrams and ui-presentations follow the app's global
26
+ `NiceI18nProvider` language when their own provider is absent (they stayed English);
27
+ `NicePresentationEditor` without `lang` takes the surrounding language. Icons drawn as glyphs are
28
+ Nice icons: the `NiceGeometry` tools, Align / Group / Ungroup / Layout of the diagram toolbar, the
29
+ presentation grid toggle, zoom and "Add Track" of `NiceDMXTimeline`. icons-gaming and
30
+ icons-filetypes name an icon by its `title` and are decorative without one, like `createIcon`
31
+ (screen readers read "GiHeart" or "DOCX file icon"); new optional `title` on the gaming icon props.
32
+
33
+ **Hook dependencies (161 → 0).** Fixed along the way: `NiceLowPowerMode` re-rendered forever from
34
+ mount and overflowed the stack on wake; `GenericPlayer` / `NiceGenericPlayerStage` restarted the
35
+ track on a volume change; `NiceDMXTimeline` called `onPlay` on every frame and missed cues added
36
+ during playback; `NiceConflictResolution` `autoResolve` never resolved; `NiceMAM` selected by the
37
+ previous filter; `NiceRulesEngine` "Run All" kept only the last result; `NiceCodeEditor` saved with
38
+ the first render's `onSave`; a consumer's cursor handler switched off `NiceCodeEditorAI`
39
+ completion; `useRadioStream` lost a volume set before the first play; `NiceGameEditor` shortcuts,
40
+ `useVoiceControl` continuous sessions, `NiceDeviceTrust` with a host storing the score.
41
+
42
+ ## 1.2.1
43
+
44
+ ### Patch Changes
45
+
46
+ - 8c82c30: 1.2.1 — the decisions that waited since 1.2.0, and the debts the CI gates were holding. One API
47
+ change, agreed: `NiceIconSuggestorAI` suggests Nice icons only. Two stylesheets to import if you use
48
+ the packages: `@nice2dev/social/style.css`, `@nice2dev/ui-science/style.css`. Upgrade notes:
49
+ `documentation/omniverk/OMNIVERK_UPGRADE_1.2.1.md`.
50
+
51
+ **Tree-shaking.** An import from a package no longer brings the whole package. Module-level
52
+ statements that may have a side effect — a `displayName` assigned after each component (now
53
+ `Object.assign` inside `/* @__PURE__ */`, or under a development check for plain functions),
54
+ unannotated `forwardRef` / `memo` / `createContext` / icon-factory calls, spreads, registries filled
55
+ at import, `<style>` tags injected at import, a direct `eval` in ui-math — are gone. One import from
56
+ each of 53 packages: 5066.5 kB → 1171.6 kB (`@nice2dev/ui` 1928.2 kB → 12.9 kB, icon packs 48–275 kB
57
+ → 1.6–2.6 kB). `defaultViewRegistry` has the Nice\* input controls from the start, each loading its
58
+ module on first render; sixteen controls add their styles to `<head>` on first render.
59
+
60
+ **Dead code.** 405 source files no entry point imported (never on npm) are deleted;
61
+ `NiceClipboardStack` (`@nice2dev/ui`) and `NiceSampleBrowser` (`@nice2dev/ui-audio`), finished and
62
+ documented, are exported.
63
+
64
+ **ui-mobile draws Nice icons.** New peer `@nice2dev/icons ^1.2.0`; `TabIcons`, `DrawerIcons`,
65
+ `HeaderIcons`, the `NiceSwipeActions` presets and the `NiceInfiniteScroll` states are `Ntd*` icons
66
+ instead of pasted `<svg>`, simple glyphs are CSS marks. `@nice2dev/icons` adds `NtdReply` and
67
+ `NtdInbox`; an icon no longer throws without `window.matchMedia`.
68
+
69
+ **`NiceIconSuggestorAI` (breaking, agreed).** `AIIconLibrary` is `'nice'`; `svgPath` and `unicode`
70
+ left `AISuggestedIcon`; the preview is a `<NiceIcon>`, "Copy SVG" is "Copy JSX".
71
+
72
+ **Peer ranges.** Every internal `@nice2dev/*` peer is `^1.2.0` (was as wide as `^1.0.20`), except
73
+ `@nice2dev/icons ^1.2.1` where a package draws an icon new in 1.2.1: ui-mobile (`NtdReply`,
74
+ `NtdInbox`), ui-audio (`NtdMicrophoneOff`) and ui-security (`NtdBackspace`). ui-security, licensing
75
+ (`^1.2.0`) and templates (`^1.2.0`) gain the `@nice2dev/icons` peer: their glyph icons (`⌫`, `←`,
76
+ `→`, `↑`, `↓`) are Nice icons now. `@nice2dev/icons` adds `NtdBackspace`.
77
+
78
+ **Class names inside components.** 834 class names in library stylesheets had no `nice-` prefix
79
+ (`.tab-button`, `.status-badge`, `.selected`, …) and styled every element of that name on the page.
80
+ They follow `nice-{block}__{element}--{modifier}` now — no change on screen. The class theme hooks
81
+ next to `[data-theme]` on seven controls (`dark` on the AI chart / data grid / code and HTML
82
+ editors / form builder, `light-theme` on `NiceMAM` and `NiceVideoAnnotator`) are `nice-dark` /
83
+ `nice-light`; `NiceLowPowerMode` marks `<html>` with `nice-lpm-no-animations` /
84
+ `nice-lpm-reduced-quality`; the `NiceVideoEditor` of `@nice2dev/ui-video` styles its elements under
85
+ `nice-video-studio`, so they no longer collide with the `NiceVideoEditor` of `@nice2dev/ui`.
86
+ Rules for classes nothing rendered are gone, among them the stylesheet of `NiceGanttChart`, which
87
+ left ui-erp in 1.0.10.
88
+
89
+ **Translations.** About 3,400 UI texts in 32 packages were hard-coded; they go through `t()` now,
90
+ with the English text unchanged and pl / de / fr / es / uk in `@nice2dev/i18n` (ui-diagrams,
91
+ ui-presentations and ui-math in their own dictionaries). In `@nice2dev/events` and
92
+ `@nice2dev/admin` that includes column headers, option labels and the statuses and roles they used
93
+ to print raw (`checked-in`, `no-show`) — English labels for those come from the dictionary. ui-3d and ui-graphics read the app's
94
+ global translator, as ui-audio does since 1.2.0 — their own provider overrides it. ui-audio's
95
+ controls used 631 keys the dictionary did not have (English in every language), and the radio
96
+ player showed raw keys (`search_stations`). Texts that were hard-coded **in Polish** — in
97
+ `NiceSidebarNav`, `NiceNetworkDiscoveryPanel`, the backup and smart-home controls,
98
+ `NiceSavedQueryPanel`, `NiceNotificationPreferences`, the ui-buildings panels, the ui-printing
99
+ template editor and the photo editor — default to English now; Polish is in the dictionary. English
100
+ plurals glued on (`result{s}`) are two keys. `presentationsTranslator(lang)` (ui-presentations)
101
+ translates outside its provider.
102
+
103
+ **List props are optional.** 170 list props (`items`, `columns`, `data`, …) in 15 packages that
104
+ were required without a default are optional, with a stable empty default; every one of those
105
+ controls mounts without data (tested). `NiceStockChart` without data no longer computes NaN
106
+ coordinates.
107
+
108
+ **Unstyled components get stylesheets.** 98 components rendered class names no stylesheet had —
109
+ admin panels, auth forms, events rounds and pickers, the ERP invoice / purchase order / VAT table /
110
+ HR dashboard, the radio player, `NiceInbox`, PWA prompts, `NiceViewRenderer`, shells, smart-home,
111
+ … The fixed layout that sat in inline style objects is in classes now, so an app can override it;
112
+ values that depend on data stay inline. **`@nice2dev/social` and `@nice2dev/ui-science` ship a
113
+ stylesheet — import `@nice2dev/social/style.css` / `@nice2dev/ui-science/style.css`.** ui-science's
114
+ `sideEffects` is `["*.css"]`. Colours, spacing and radii in every stylesheet come from theme tokens
115
+ (visible in dark themes: `NiceRecipe` difficulty badges, transcoder states, warehouse heat map).
116
+
117
+ **Icons only from Nice.** About 130 glyphs (`×`, `→`, `↑`) and 100 pasted `<svg>` used as icons
118
+ are Nice icons; 11 `<svg>` stay as the control's own graphic (logos, spinners, checkbox mark). `NiceIcon`
119
+ gains `pin`, `mic-off`, `log-out`, `chevrons-left`, `chevrons-right`, `zoom-in`, `zoom-out`,
120
+ `navigation`, `refresh-ccw`, `minimize` and `smartphone`; `@nice2dev/icons` gains
121
+ `NtdMicrophoneOff`. The `icon` fields of the module, menu, tab, mode, quick-action, scene and admin
122
+ widget DTOs, notification channels and view toolbar items are rendered through `NiceIcon` — they
123
+ used to be printed as text. `NiceRatings` `thumbs` draws shapes instead of emoji.
124
+
125
+ **Dialogs instead of `window.prompt` / `confirm` / `alert`.** New `useNiceDialogs()` — `prompt`,
126
+ `confirm` and `alert` as promises on `NicePromptDialog` / `NiceConfirmDialog` — and
127
+ `NiceConfirmDialog.showReject`. Seventeen controls asked through the browser's blocking dialogs and
128
+ use the library's now (`NiceFileManager`, `NiceHtmlEditor`, `NiceViewRenderer` action `confirm`, the 3D,
129
+ vector and pixel editors, …). Keys pressed in `NicePromptDialog` / `NiceConfirmDialog` no longer
130
+ reach the page's shortcuts (Escape still closes them), and `NicePromptDialog`'s backdrop covers the
131
+ whole viewport.
132
+
133
+ **Smaller fixes.** `NiceFileManager.items` is optional. `NiceMLStudio` status badges: `draft`,
134
+ `registered`, `archived`, `cancelled` get a neutral colour, deploying / stopped deployments theirs.
135
+ Component docs (`doc.json`) list only props the components have. `NiceMobileTabs`: tabs beyond
136
+ `maxTabs` open from "More" (it only logged). `NiceInventoryManager`: new `onCreatePurchaseOrder`;
137
+ without it the "Create PO" button is gone (it only logged). Keyboard: `NiceNavShell` tabs follow the
138
+ WAI-ARIA tabs pattern, clickable `NiceDeviceTile` / `NiceCardGroup` cards and animated icons with
139
+ `trigger="click"` are focusable. ui-math: `CalculatorService.evaluate` parses instead of running
140
+ `new Function` (`asin`, `sinh`, `ceil`, exponents and `angleUnit` were wrong); `ExpressionParser`
141
+ takes `functions`, `constants`, `ignoreCase` and `strict`, multiplies implicitly (`2x`) and reads `^`
142
+ right to left. `InvoiceLine.vatRate` and `VatRate.rate` are documented as fractions (0.23).
143
+ `AmqpServerBinding` is `Record<string, never>`.
144
+
3
145
  ## 1.2.0
4
146
 
5
147
  ### Minor Changes
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var framerMotion = require('framer-motion');
5
5
  var React = require('react');
6
+ var i18n = require('../core/i18n.js');
6
7
  var animationHelper = require('./animationHelper.js');
7
8
  var characterTypes = require('./characterTypes.js');
8
9
 
@@ -11,6 +12,7 @@ const NiceAnimatedPerson = ({ id, name, size, score = 10, startPose = 'idle', on
11
12
  // colors prop is destructured for API completeness but not yet fully wired into character defaults.
12
13
  }) => {
13
14
  var _a, _b, _c, _d, _e;
15
+ const { t } = i18n.useNiceTranslation();
14
16
  const fx = animationHelper.usePersonControls();
15
17
  const effectiveStartPose = pose !== null && pose !== void 0 ? pose : startPose;
16
18
  const effectiveCharacter = character !== null && character !== void 0 ? character : config;
@@ -22,7 +24,7 @@ const NiceAnimatedPerson = ({ id, name, size, score = 10, startPose = 'idle', on
22
24
  }, [fx, onReady, effectiveStartPose]);
23
25
  const W = (_c = (_b = (_a = cfg.size) !== null && _a !== void 0 ? _a : effectiveSize) !== null && _b !== void 0 ? _b : characterTypes.DEFAULT_CHARACTER.size) !== null && _c !== void 0 ? _c : 180;
24
26
  const skin = characterTypes.pick(cfg.face.colors, 0, 'var(--anim-skin, #FFD2B3)');
25
- return (jsxRuntime.jsx(framerMotion.motion.div, { id: id, style: { width: W, height: W * 1.35, overflow: 'visible' }, animate: fx.root, children: jsxRuntime.jsxs("svg", { viewBox: "0 0 180 240", width: "100%", height: "100%", role: "img", "aria-label": (_e = (_d = cfg.name) !== null && _d !== void 0 ? _d : name) !== null && _e !== void 0 ? _e : 'Juror', children: [jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.body, children: [jsxRuntime.jsx("rect", { x: "68", y: "158", width: "14", height: "46", rx: "6", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsxRuntime.jsx("ellipse", { cx: "75", cy: "204", rx: "10", ry: "5", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') }), jsxRuntime.jsx("rect", { x: "98", y: "158", width: "14", height: "46", rx: "6", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsxRuntime.jsx("ellipse", { cx: "105", cy: "204", rx: "10", ry: "5", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') })] }), jsxRuntime.jsx(framerMotion.motion.g, { animate: fx.body, children: renderOutfit(cfg.outfit) }), jsxRuntime.jsx("rect", { x: "84", y: "82", width: "12", height: "12", rx: "4", fill: skin }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.leftArm, style: { transformOrigin: '64px 96px' }, children: [jsxRuntime.jsx("rect", { x: "36", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsxRuntime.jsx("circle", { cx: "44", cy: "136", r: "7", fill: skin })] }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.rightArm, style: { transformOrigin: '116px 96px' }, children: [jsxRuntime.jsx("rect", { x: "128", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsxRuntime.jsx("circle", { cx: "136", cy: "136", r: "7", fill: skin }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.card, children: [jsxRuntime.jsx("rect", { x: "126", y: "50", width: "42", height: "30", rx: "4", fill: "var(--nice-color-surface, #fff)", stroke: "var(--nice-text-primary, #111)", strokeWidth: 1.2 }), jsxRuntime.jsx("text", { x: "147", y: "70", textAnchor: "middle", fontSize: "16", fontWeight: 700, fill: "var(--nice-text-primary, #111)", children: String(score) })] }), renderProp(cfg.prop)] }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.head, children: [renderHairBack(cfg.hair), renderFace(cfg.face), jsxRuntime.jsx(framerMotion.motion.g, { animate: fx.eyes, children: renderEyes(cfg.eyes) }), renderNose(cfg.nose), jsxRuntime.jsx(framerMotion.motion.g, { animate: fx.mouth, children: renderMouth(cfg.mouth) }), renderHeadwear(cfg.headwear)] }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.curse, initial: { opacity: 0 }, children: [jsxRuntime.jsx("rect", { x: "62", y: "10", width: "56", height: "22", rx: "8", fill: "var(--nice-text-primary, #111)" }), jsxRuntime.jsx("text", { x: "90", y: "26", textAnchor: "middle", fontSize: "12", fill: "var(--nice-text-inverse, #fff)", children: "#!%&*" }), jsxRuntime.jsx("path", { d: "M82 32 l4 8 l4 -8", fill: "var(--nice-text-primary, #111)" })] })] }) }));
27
+ return (jsxRuntime.jsx(framerMotion.motion.div, { id: id, style: { width: W, height: W * 1.35, overflow: 'visible' }, animate: fx.root, children: jsxRuntime.jsxs("svg", { viewBox: "0 0 180 240", width: "100%", height: "100%", role: "img", "aria-label": (_e = (_d = cfg.name) !== null && _d !== void 0 ? _d : name) !== null && _e !== void 0 ? _e : t('animatedPerson.juror', 'Juror'), children: [jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.body, children: [jsxRuntime.jsx("rect", { x: "68", y: "158", width: "14", height: "46", rx: "6", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsxRuntime.jsx("ellipse", { cx: "75", cy: "204", rx: "10", ry: "5", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') }), jsxRuntime.jsx("rect", { x: "98", y: "158", width: "14", height: "46", rx: "6", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsxRuntime.jsx("ellipse", { cx: "105", cy: "204", rx: "10", ry: "5", fill: characterTypes.pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') })] }), jsxRuntime.jsx(framerMotion.motion.g, { animate: fx.body, children: renderOutfit(cfg.outfit) }), jsxRuntime.jsx("rect", { x: "84", y: "82", width: "12", height: "12", rx: "4", fill: skin }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.leftArm, style: { transformOrigin: '64px 96px' }, children: [jsxRuntime.jsx("rect", { x: "36", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsxRuntime.jsx("circle", { cx: "44", cy: "136", r: "7", fill: skin })] }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.rightArm, style: { transformOrigin: '116px 96px' }, children: [jsxRuntime.jsx("rect", { x: "128", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsxRuntime.jsx("circle", { cx: "136", cy: "136", r: "7", fill: skin }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.card, children: [jsxRuntime.jsx("rect", { x: "126", y: "50", width: "42", height: "30", rx: "4", fill: "var(--nice-color-surface, #fff)", stroke: "var(--nice-text-primary, #111)", strokeWidth: 1.2 }), jsxRuntime.jsx("text", { x: "147", y: "70", textAnchor: "middle", fontSize: "16", fontWeight: 700, fill: "var(--nice-text-primary, #111)", children: String(score) })] }), renderProp(cfg.prop)] }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.head, children: [renderHairBack(cfg.hair), renderFace(cfg.face), jsxRuntime.jsx(framerMotion.motion.g, { animate: fx.eyes, children: renderEyes(cfg.eyes) }), renderNose(cfg.nose), jsxRuntime.jsx(framerMotion.motion.g, { animate: fx.mouth, children: renderMouth(cfg.mouth) }), renderHeadwear(cfg.headwear)] }), jsxRuntime.jsxs(framerMotion.motion.g, { animate: fx.curse, initial: { opacity: 0 }, children: [jsxRuntime.jsx("rect", { x: "62", y: "10", width: "56", height: "22", rx: "8", fill: "var(--nice-text-primary, #111)" }), jsxRuntime.jsx("text", { x: "90", y: "26", textAnchor: "middle", fontSize: "12", fill: "var(--nice-text-inverse, #fff)", children: "#!%&*" }), jsxRuntime.jsx("path", { d: "M82 32 l4 8 l4 -8", fill: "var(--nice-text-primary, #111)" })] })] }) }));
26
28
  };
27
29
  /* ---------------- RENDERERS (redesigned proportions) ---------------- */
28
30
  function renderOutfit(cfg) {
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var ui = require('@nice2dev/ui');
5
5
  var React = require('react');
6
+ var i18n = require('../core/i18n.js');
6
7
  var AnimatedPerson = require('./AnimatedPerson.js');
7
8
  var characterTypes = require('./characterTypes.js');
8
9
  var choreoDSL = require('./choreoDSL.js');
@@ -35,6 +36,7 @@ function makeAudience(n) {
35
36
  }
36
37
  /** Grid of animated audience members with wave/cheer actions. */
37
38
  const NiceAudience = ({ count = 36, columns = 12 }) => {
39
+ const { t } = i18n.useNiceTranslation();
38
40
  const people = React.useMemo(() => makeAudience(count), [count]);
39
41
  const refs = React.useRef([]);
40
42
  refs.current = [];
@@ -59,7 +61,7 @@ const NiceAudience = ({ count = 36, columns = 12 }) => {
59
61
  display: 'grid',
60
62
  gap: 8,
61
63
  gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
62
- }, children: people.map((c, i) => (jsxRuntime.jsx(AnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", size: "sm", onClick: wave, children: "Wave" }), jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", size: "sm", onClick: cheer, children: "Cheer" })] })] }));
64
+ }, children: people.map((c, i) => (jsxRuntime.jsx(AnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", size: "sm", onClick: wave, children: t('audience.wave', 'Wave') }), jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", size: "sm", onClick: cheer, children: t('audience.cheer', 'Cheer') })] })] }));
63
65
  };
64
66
 
65
67
  module.exports = NiceAudience;
@@ -9,7 +9,7 @@ var RiggedBody = require('./RiggedBody.js');
9
9
  var rigMath = require('./rigMath.js');
10
10
  var rigTypes = require('./rigTypes.js');
11
11
 
12
- const NiceRigPlayer = React.forwardRef(({ character, rig, pose, size, debug }, ref) => {
12
+ const NiceRigPlayer = /* @__PURE__ */ React.forwardRef(({ character, rig, pose, size, debug }, ref) => {
13
13
  var _a;
14
14
  const [current, setCurrent] = React.useState({ ...rigTypes.DEFAULT_POSE, ...(pose !== null && pose !== void 0 ? pose : {}) });
15
15
  const r = rig !== null && rig !== void 0 ? rig : rigTypes.DEFAULT_RIG;
@@ -3,66 +3,78 @@
3
3
  var rigTypes = require('./rigTypes.js');
4
4
 
5
5
  /** Slim proportions preset. */
6
- const RIG_SLIM = {
7
- ...rigTypes.DEFAULT_RIG,
8
- lUpperArm: { ...rigTypes.DEFAULT_RIG.lUpperArm, length: 0.11 },
9
- lForearm: { ...rigTypes.DEFAULT_RIG.lForearm, length: 0.11 },
10
- rUpperArm: { ...rigTypes.DEFAULT_RIG.rUpperArm, length: 0.11 },
11
- rForearm: { ...rigTypes.DEFAULT_RIG.rForearm, length: 0.11 },
12
- lThigh: { ...rigTypes.DEFAULT_RIG.lThigh, length: 0.14 },
13
- lShin: { ...rigTypes.DEFAULT_RIG.lShin, length: 0.14 },
14
- rThigh: { ...rigTypes.DEFAULT_RIG.rThigh, length: 0.14 },
15
- rShin: { ...rigTypes.DEFAULT_RIG.rShin, length: 0.14 },
16
- };
6
+ const RIG_SLIM = /* @__PURE__ */ (function () {
7
+ return {
8
+ ...rigTypes.DEFAULT_RIG,
9
+ lUpperArm: { ...rigTypes.DEFAULT_RIG.lUpperArm, length: 0.11 },
10
+ lForearm: { ...rigTypes.DEFAULT_RIG.lForearm, length: 0.11 },
11
+ rUpperArm: { ...rigTypes.DEFAULT_RIG.rUpperArm, length: 0.11 },
12
+ rForearm: { ...rigTypes.DEFAULT_RIG.rForearm, length: 0.11 },
13
+ lThigh: { ...rigTypes.DEFAULT_RIG.lThigh, length: 0.14 },
14
+ lShin: { ...rigTypes.DEFAULT_RIG.lShin, length: 0.14 },
15
+ rThigh: { ...rigTypes.DEFAULT_RIG.rThigh, length: 0.14 },
16
+ rShin: { ...rigTypes.DEFAULT_RIG.rShin, length: 0.14 },
17
+ };
18
+ })();
17
19
  /** Broad shoulders preset. */
18
- const RIG_HERO = {
19
- ...rigTypes.DEFAULT_RIG,
20
- lShoulder: { ...rigTypes.DEFAULT_RIG.lShoulder, offset: [-0.13, 0] },
21
- rShoulder: { ...rigTypes.DEFAULT_RIG.rShoulder, offset: [0.13, 0] },
22
- lUpperArm: { ...rigTypes.DEFAULT_RIG.lUpperArm, length: 0.13 },
23
- rUpperArm: { ...rigTypes.DEFAULT_RIG.rUpperArm, length: 0.13 },
24
- };
20
+ const RIG_HERO = /* @__PURE__ */ (function () {
21
+ return {
22
+ ...rigTypes.DEFAULT_RIG,
23
+ lShoulder: { ...rigTypes.DEFAULT_RIG.lShoulder, offset: [-0.13, 0] },
24
+ rShoulder: { ...rigTypes.DEFAULT_RIG.rShoulder, offset: [0.13, 0] },
25
+ lUpperArm: { ...rigTypes.DEFAULT_RIG.lUpperArm, length: 0.13 },
26
+ rUpperArm: { ...rigTypes.DEFAULT_RIG.rUpperArm, length: 0.13 },
27
+ };
28
+ })();
25
29
  /** Robot-like short limbs preset. */
26
- const RIG_ROBOT = {
27
- ...rigTypes.DEFAULT_RIG,
28
- lUpperArm: { ...rigTypes.DEFAULT_RIG.lUpperArm, length: 0.1 },
29
- lForearm: { ...rigTypes.DEFAULT_RIG.lForearm, length: 0.1 },
30
- rUpperArm: { ...rigTypes.DEFAULT_RIG.rUpperArm, length: 0.1 },
31
- rForearm: { ...rigTypes.DEFAULT_RIG.rForearm, length: 0.1 },
32
- lThigh: { ...rigTypes.DEFAULT_RIG.lThigh, length: 0.13 },
33
- lShin: { ...rigTypes.DEFAULT_RIG.lShin, length: 0.13 },
34
- rThigh: { ...rigTypes.DEFAULT_RIG.rThigh, length: 0.13 },
35
- rShin: { ...rigTypes.DEFAULT_RIG.rShin, length: 0.13 },
36
- };
30
+ const RIG_ROBOT = /* @__PURE__ */ (function () {
31
+ return {
32
+ ...rigTypes.DEFAULT_RIG,
33
+ lUpperArm: { ...rigTypes.DEFAULT_RIG.lUpperArm, length: 0.1 },
34
+ lForearm: { ...rigTypes.DEFAULT_RIG.lForearm, length: 0.1 },
35
+ rUpperArm: { ...rigTypes.DEFAULT_RIG.rUpperArm, length: 0.1 },
36
+ rForearm: { ...rigTypes.DEFAULT_RIG.rForearm, length: 0.1 },
37
+ lThigh: { ...rigTypes.DEFAULT_RIG.lThigh, length: 0.13 },
38
+ lShin: { ...rigTypes.DEFAULT_RIG.lShin, length: 0.13 },
39
+ rThigh: { ...rigTypes.DEFAULT_RIG.rThigh, length: 0.13 },
40
+ rShin: { ...rigTypes.DEFAULT_RIG.rShin, length: 0.13 },
41
+ };
42
+ })();
37
43
  /** Happy pose. */
38
- const POSE_HAPPY = {
39
- ...rigTypes.DEFAULT_POSE,
40
- spineUpper: 80,
41
- neck: 70,
42
- head: 60,
43
- lUpperArm: 60,
44
- lForearm: 20,
45
- rUpperArm: 120,
46
- rForearm: 160,
47
- };
44
+ const POSE_HAPPY = /* @__PURE__ */ (function () {
45
+ return {
46
+ ...rigTypes.DEFAULT_POSE,
47
+ spineUpper: 80,
48
+ neck: 70,
49
+ head: 60,
50
+ lUpperArm: 60,
51
+ lForearm: 20,
52
+ rUpperArm: 120,
53
+ rForearm: 160,
54
+ };
55
+ })();
48
56
  /** Angry pose. */
49
- const POSE_ANGRY = {
50
- ...rigTypes.DEFAULT_POSE,
51
- head: 90,
52
- lUpperArm: 150,
53
- rUpperArm: 30,
54
- lForearm: 160,
55
- rForearm: 20,
56
- };
57
+ const POSE_ANGRY = /* @__PURE__ */ (function () {
58
+ return {
59
+ ...rigTypes.DEFAULT_POSE,
60
+ head: 90,
61
+ lUpperArm: 150,
62
+ rUpperArm: 30,
63
+ lForearm: 160,
64
+ rForearm: 20,
65
+ };
66
+ })();
57
67
  /** Dance pose A. */
58
- const POSE_DANCE_A = {
59
- ...rigTypes.DEFAULT_POSE,
60
- pelvis: 100,
61
- lThigh: 120,
62
- rThigh: 70,
63
- lUpperArm: 30,
64
- rUpperArm: 150,
65
- };
68
+ const POSE_DANCE_A = /* @__PURE__ */ (function () {
69
+ return {
70
+ ...rigTypes.DEFAULT_POSE,
71
+ pelvis: 100,
72
+ lThigh: 120,
73
+ rThigh: 70,
74
+ lUpperArm: 30,
75
+ rUpperArm: 150,
76
+ };
77
+ })();
66
78
 
67
79
  exports.POSE_ANGRY = POSE_ANGRY;
68
80
  exports.POSE_DANCE_A = POSE_DANCE_A;
@@ -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,23 +544,25 @@ 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);
549
+ const { endStroke } = engine;
547
550
  // Override endStroke to fire callback
548
551
  React.useEffect(() => {
549
552
  if (!canvas) {
550
553
  return;
551
554
  }
552
555
  const onUp = () => {
553
- const stroke = engine.endStroke();
556
+ const stroke = endStroke();
554
557
  if (stroke && onStrokeEnd) {
555
558
  onStrokeEnd(stroke);
556
559
  }
557
560
  };
558
561
  canvas.addEventListener('pointerup', onUp);
559
562
  return () => canvas.removeEventListener('pointerup', onUp);
560
- }, [canvas, engine.endStroke, onStrokeEnd]);
563
+ }, [canvas, endStroke, onStrokeEnd]);
561
564
  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) => {
565
+ 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
566
  var _a, _b;
564
567
  return (jsxRuntime.jsx("button", { type: "button", onClick: () => engine.applyPreset(name), style: {
565
568
  padding: '2px 6px',
@@ -572,13 +575,13 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
572
575
  : 'transparent',
573
576
  color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? 'var(--nice-color-white, #fff)' : 'inherit',
574
577
  }, children: BRUSH_PRESETS[name].name }, name));
575
- }) }), 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({
578
+ }) }), jsxRuntime.jsxs("label", { children: [t('brushEngine.sizePx', 'Size: {size}px').replace('{size}', String(s.size)), 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: [t('brushEngine.opacity', 'Opacity: {opacity}%').replace('{opacity}', String(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: [t('brushEngine.flow', 'Flow: {flow}%').replace('{flow}', String(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: [t('brushEngine.hardness', 'Hardness: {hardness}%').replace('{hardness}', String(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: [t('brushEngine.smoothing', 'Smoothing: {smoothing}%').replace('{smoothing}', String(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: [t('brushEngine.tip', '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: [t('brushEngine.roundness', 'Roundness: {roundness}%').replace('{roundness}', String(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: [t('brushEngine.source', 'Source:'), jsxRuntime.jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
576
579
  ...s,
577
580
  sizeDynamics: {
578
581
  ...s.sizeDynamics,
579
582
  source: e.target.value,
580
583
  },
581
- }), 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"] })] }));
584
+ }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "none", children: t('brushEngine.off', '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: [t('brushEngine.scatter', 'Scatter: {scatter}%').replace('{scatter}', String(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: [t('brushEngine.blend', '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')] })] }));
582
585
  };
583
586
 
584
587
  exports.BRUSH_PRESETS = BRUSH_PRESETS;
@@ -171,10 +171,14 @@ function useCollaboration(transport, roomId, currentUser) {
171
171
  const [users, setUsers] = React.useState([]);
172
172
  const [connected, setConnected] = React.useState(false);
173
173
  const operationHandlers = React.useRef([]);
174
+ // the user as it is when the room is joined: a new object with the same id must not rejoin
175
+ const currentUserRef = React.useRef(currentUser);
176
+ currentUserRef.current = currentUser;
174
177
  React.useEffect(() => {
175
- transport.connect(roomId, currentUser.id);
178
+ const user = currentUserRef.current;
179
+ transport.connect(roomId, user.id);
176
180
  setConnected(true);
177
- transport.send({ type: 'join', user: currentUser });
181
+ transport.send({ type: 'join', user });
178
182
  const unsubscribe = transport.onMessage((msg) => {
179
183
  switch (msg.type) {
180
184
  case 'join':
@@ -204,17 +208,18 @@ function useCollaboration(transport, roomId, currentUser) {
204
208
  }
205
209
  });
206
210
  return () => {
207
- transport.send({ type: 'leave', userId: currentUser.id });
211
+ transport.send({ type: 'leave', userId: user.id });
208
212
  unsubscribe();
209
213
  transport.disconnect();
210
214
  setConnected(false);
211
215
  };
212
- }, [roomId, currentUser.id]);
213
- const sendCursor = React.useCallback((x, y) => transport.send({ type: 'cursor', userId: currentUser.id, x, y }), [currentUser.id]);
216
+ // a new transport reconnects (until 1.2.2 it was ignored and messages went to the old one)
217
+ }, [transport, roomId, currentUser.id]);
218
+ const sendCursor = React.useCallback((x, y) => transport.send({ type: 'cursor', userId: currentUser.id, x, y }), [transport, currentUser.id]);
214
219
  const sendOperation = React.useCallback((op) => transport.send({
215
220
  type: 'operation',
216
221
  operation: { ...op, timestamp: Date.now(), userId: currentUser.id },
217
- }), [currentUser.id]);
222
+ }), [transport, currentUser.id]);
218
223
  const onOperation = React.useCallback((handler) => {
219
224
  operationHandlers.current.push(handler);
220
225
  return () => {
@@ -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
 
@@ -20,6 +20,27 @@ const Minimap = ({ canvasRef, imageDataUrl, documentWidth, documentHeight, viewp
20
20
  const scale = Math.min(scaleX, scaleY);
21
21
  // Draw minimap
22
22
  React.useEffect(() => {
23
+ // inside the effect: it draws with this run's viewport, scale and size
24
+ const drawViewport = (ctx) => {
25
+ // Draw viewport rect
26
+ const vx = viewport.x * scale;
27
+ const vy = viewport.y * scale;
28
+ const vw = viewport.width * scale;
29
+ const vh = viewport.height * scale;
30
+ ctx.strokeStyle = uiCore.resolveCssColor('var(--nice-primary-tint-80, rgba(59, 130, 246, 0.8))', ctx.canvas);
31
+ ctx.lineWidth = 1.5;
32
+ ctx.strokeRect(vx, vy, vw, vh);
33
+ // Dim areas outside viewport
34
+ ctx.fillStyle = uiCore.resolveCssColor('var(--nice-overlay-30, rgba(0, 0, 0, 0.3))', ctx.canvas);
35
+ // Top
36
+ ctx.fillRect(0, 0, mapW, vy);
37
+ // Bottom
38
+ ctx.fillRect(0, vy + vh, mapW, mapH - vy - vh);
39
+ // Left
40
+ ctx.fillRect(0, vy, vx, vh);
41
+ // Right
42
+ ctx.fillRect(vx + vw, vy, mapW - vx - vw, vh);
43
+ };
23
44
  if (!visible) {
24
45
  return;
25
46
  }
@@ -64,26 +85,6 @@ const Minimap = ({ canvasRef, imageDataUrl, documentWidth, documentHeight, viewp
64
85
  mapW,
65
86
  mapH,
66
87
  ]);
67
- function drawViewport(ctx) {
68
- // Draw viewport rect
69
- const vx = viewport.x * scale;
70
- const vy = viewport.y * scale;
71
- const vw = viewport.width * scale;
72
- const vh = viewport.height * scale;
73
- ctx.strokeStyle = uiCore.resolveCssColor('var(--nice-primary-tint-80, rgba(59, 130, 246, 0.8))', ctx.canvas);
74
- ctx.lineWidth = 1.5;
75
- ctx.strokeRect(vx, vy, vw, vh);
76
- // Dim areas outside viewport
77
- ctx.fillStyle = uiCore.resolveCssColor('var(--nice-overlay-30, rgba(0, 0, 0, 0.3))', ctx.canvas);
78
- // Top
79
- ctx.fillRect(0, 0, mapW, vy);
80
- // Bottom
81
- ctx.fillRect(0, vy + vh, mapW, mapH - vy - vh);
82
- // Left
83
- ctx.fillRect(0, vy, vx, vh);
84
- // Right
85
- ctx.fillRect(vx + vw, vy, mapW - vx - vw, vh);
86
- }
87
88
  const handlePointerDown = (e) => {
88
89
  isDragging.current = true;
89
90
  updateViewportFromPointer(e);
@@ -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) {