@natsuneko-laboratory/react-native-desktop-navigation 0.1.0-alpha.12 → 0.1.0-alpha.14

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.
@@ -92,17 +92,40 @@ function NativeSurface({ configuration, slots, icons = [], footer, onFooterHeigh
92
92
  }
93
93
  const [acknowledgement, acknowledge] = react_1.default.useReducer((n) => n + 1, 0);
94
94
  const serialized = JSON.stringify(configuration);
95
- const version = react_1.default.useRef({ serialized, acknowledgement, revision: 0 });
95
+ // Selection and acknowledgements do not invalidate sidebar icon positions.
96
+ // Other configuration changes may move/resize rows, so await fresh geometry.
97
+ const iconLayoutConfiguration = JSON.stringify({
98
+ ...configuration,
99
+ activeKey: undefined,
100
+ items: configuration.items.map((item) => ({
101
+ ...item,
102
+ // Native icons occupy fixed square slots. Switching a symbol/glyph,
103
+ // image or tint on selection changes its contents, not row geometry.
104
+ icon: item.icon && { type: item.icon.type, size: item.icon.size },
105
+ })),
106
+ });
107
+ const version = react_1.default.useRef({
108
+ serialized,
109
+ acknowledgement,
110
+ revision: 0,
111
+ iconLayoutConfiguration,
112
+ iconLayoutRevision: 0,
113
+ });
96
114
  if (version.current.serialized !== serialized ||
97
115
  version.current.acknowledgement !== acknowledgement) {
98
116
  version.current = {
99
117
  serialized,
100
118
  acknowledgement,
101
119
  revision: version.current.revision + 1,
120
+ iconLayoutConfiguration,
121
+ iconLayoutRevision: version.current.iconLayoutRevision +
122
+ Number(version.current.iconLayoutConfiguration !== iconLayoutConfiguration),
102
123
  };
103
124
  }
104
125
  const revision = version.current.revision;
105
- const [layout, setLayout] = react_1.default.useState(null);
126
+ const iconLayoutRevision = version.current.iconLayoutRevision;
127
+ const [measurement, setMeasurement] = react_1.default.useState(null);
128
+ const layout = measurement?.event;
106
129
  const footerFrame = layout?.footerFrame;
107
130
  const footerPlaced = !!footerFrame && footerFrame.width > 0;
108
131
  return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, style], onLayout: onLayout, children: [(0, jsx_runtime_1.jsx)(DesktopNavigationHostNativeComponent_1.default, { style: react_native_1.StyleSheet.absoluteFillObject, configuration: JSON.stringify({ ...configuration, revision }), onNavigationEvent: (event) => {
@@ -111,9 +134,9 @@ function NativeSurface({ configuration, slots, icons = [], footer, onFooterHeigh
111
134
  return;
112
135
  if (message.type === 'layout') {
113
136
  onMeasurements?.(message.frames);
114
- setLayout((previous) => JSON.stringify(previous) === JSON.stringify(message)
137
+ setMeasurement((previous) => JSON.stringify(previous?.event) === JSON.stringify(message)
115
138
  ? previous
116
- : message);
139
+ : { event: message, iconLayoutRevision });
117
140
  }
118
141
  else {
119
142
  onRequest(message);
@@ -154,9 +177,10 @@ function NativeSurface({ configuration, slots, icons = [], footer, onFooterHeigh
154
177
  opacity: 0,
155
178
  },
156
179
  ], children: footer })), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: react_native_1.StyleSheet.absoluteFillObject, children: icons.map((icon) => {
157
- // Never display a removed/resized icon at a previous revision's position.
158
- const geometry = layout?.revision === revision
159
- ? layout.iconFrames?.[icon.key]
180
+ // Keep SVGs mounted across selection-only revisions, but never reuse
181
+ // positions after a layout configuration change (even if reverted).
182
+ const geometry = measurement?.iconLayoutRevision === iconLayoutRevision
183
+ ? layout?.iconFrames?.[icon.key]
160
184
  : undefined;
161
185
  if (!geometry ||
162
186
  geometry.clip.width <= 0 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@natsuneko-laboratory/react-native-desktop-navigation",
3
- "version": "0.1.0-alpha.12",
3
+ "version": "0.1.0-alpha.14",
4
4
  "description": "Desktop-first navigation for React Native macOS and Windows",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -211,7 +211,25 @@ export function NativeSurface({
211
211
  }
212
212
  const [acknowledgement, acknowledge] = React.useReducer((n) => n + 1, 0);
213
213
  const serialized = JSON.stringify(configuration);
214
- const version = React.useRef({ serialized, acknowledgement, revision: 0 });
214
+ // Selection and acknowledgements do not invalidate sidebar icon positions.
215
+ // Other configuration changes may move/resize rows, so await fresh geometry.
216
+ const iconLayoutConfiguration = JSON.stringify({
217
+ ...configuration,
218
+ activeKey: undefined,
219
+ items: configuration.items.map((item) => ({
220
+ ...item,
221
+ // Native icons occupy fixed square slots. Switching a symbol/glyph,
222
+ // image or tint on selection changes its contents, not row geometry.
223
+ icon: item.icon && { type: item.icon.type, size: item.icon.size },
224
+ })),
225
+ });
226
+ const version = React.useRef({
227
+ serialized,
228
+ acknowledgement,
229
+ revision: 0,
230
+ iconLayoutConfiguration,
231
+ iconLayoutRevision: 0,
232
+ });
215
233
  if (
216
234
  version.current.serialized !== serialized ||
217
235
  version.current.acknowledgement !== acknowledgement
@@ -220,10 +238,21 @@ export function NativeSurface({
220
238
  serialized,
221
239
  acknowledgement,
222
240
  revision: version.current.revision + 1,
241
+ iconLayoutConfiguration,
242
+ iconLayoutRevision:
243
+ version.current.iconLayoutRevision +
244
+ Number(
245
+ version.current.iconLayoutConfiguration !== iconLayoutConfiguration,
246
+ ),
223
247
  };
224
248
  }
225
249
  const revision = version.current.revision;
226
- const [layout, setLayout] = React.useState<LayoutEvent | null>(null);
250
+ const iconLayoutRevision = version.current.iconLayoutRevision;
251
+ const [measurement, setMeasurement] = React.useState<{
252
+ event: LayoutEvent;
253
+ iconLayoutRevision: number;
254
+ } | null>(null);
255
+ const layout = measurement?.event;
227
256
  const footerFrame = layout?.footerFrame;
228
257
  const footerPlaced = !!footerFrame && footerFrame.width > 0;
229
258
  return (
@@ -236,10 +265,10 @@ export function NativeSurface({
236
265
  if (!message || message.revision !== revision) return;
237
266
  if (message.type === 'layout') {
238
267
  onMeasurements?.(message.frames);
239
- setLayout((previous) =>
240
- JSON.stringify(previous) === JSON.stringify(message)
268
+ setMeasurement((previous) =>
269
+ JSON.stringify(previous?.event) === JSON.stringify(message)
241
270
  ? previous
242
- : message,
271
+ : { event: message, iconLayoutRevision },
243
272
  );
244
273
  } else {
245
274
  onRequest(message);
@@ -321,10 +350,11 @@ export function NativeSurface({
321
350
  style={StyleSheet.absoluteFillObject}
322
351
  >
323
352
  {icons.map((icon) => {
324
- // Never display a removed/resized icon at a previous revision's position.
353
+ // Keep SVGs mounted across selection-only revisions, but never reuse
354
+ // positions after a layout configuration change (even if reverted).
325
355
  const geometry =
326
- layout?.revision === revision
327
- ? layout.iconFrames?.[icon.key]
356
+ measurement?.iconLayoutRevision === iconLayoutRevision
357
+ ? layout?.iconFrames?.[icon.key]
328
358
  : undefined;
329
359
  if (
330
360
  !geometry ||