@nice2dev/ui-printing 1.2.2 → 1.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nice2dev/ui-printing",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "React components for print management: template editor, template browser, print preview, print queue and print button",
5
5
  "keywords": [
6
6
  "printing",
@@ -47,10 +47,10 @@
47
47
  "peerDependencies": {
48
48
  "react": ">=17.0.0",
49
49
  "react-dom": ">=17.0.0",
50
- "@nice2dev/ui": "^1.2.0"
50
+ "@nice2dev/ui": "^1.3.0"
51
51
  },
52
52
  "devDependencies": {
53
- "@nice2dev/ui": "^1.2.2",
53
+ "@nice2dev/ui": "^1.3.1",
54
54
  "@types/react": "^19.2.18",
55
55
  "@types/react-dom": "^19.2.7",
56
56
  "@vitejs/plugin-react": "^4.7.0",
@@ -1,5 +1,11 @@
1
- import { NiceIcon, useNiceTranslation } from '@nice2dev/ui';
2
- import React, { useState, useMemo, useEffect } from 'react';
1
+ import {
2
+ NiceIcon,
3
+ useInputHints,
4
+ useIntent,
5
+ useNiceTranslation,
6
+ useOverlayFocus,
7
+ } from '@nice2dev/ui';
8
+ import React, { useState, useMemo, useEffect, useRef } from 'react';
3
9
  import { createPortal } from 'react-dom';
4
10
 
5
11
  import type {
@@ -259,18 +265,10 @@ export const NicePrintPreview: React.FC<NicePrintPreviewProps> = ({
259
265
  if (!asPopup || !isOpen) {
260
266
  return;
261
267
  }
262
- const handler = (e: KeyboardEvent) => {
263
- if (e.key === 'Escape') {
264
- handleClose();
265
- }
266
- };
267
- document.addEventListener('keydown', handler);
268
268
  document.body.style.overflow = 'hidden';
269
269
  return () => {
270
- document.removeEventListener('keydown', handler);
271
270
  document.body.style.overflow = '';
272
271
  };
273
- // eslint-disable-next-line react-hooks/exhaustive-deps
274
272
  }, [asPopup, isOpen]);
275
273
 
276
274
  const dataList = useMemo(() => {
@@ -285,12 +283,53 @@ export const NicePrintPreview: React.FC<NicePrintPreviewProps> = ({
285
283
 
286
284
  const currentData = dataList[entityIdx] ?? {};
287
285
 
286
+ // The popup holds focus; Escape / B close it — only the top overlay — and focus goes back to
287
+ // the opener (1.3.0, a document listener before). A pad: LB / RB between the records, LT / RT
288
+ // zoom.
289
+ const dialogRef = useRef<HTMLDivElement>(null);
290
+ const rootRef = useRef<HTMLDivElement>(null);
291
+ useOverlayFocus(dialogRef, { open: asPopup && isOpen, onClose: handleClose });
292
+ useIntent(
293
+ ['sectionPrev', 'sectionNext', 'pagePrev', 'pageNext'],
294
+ (d) => {
295
+ if (d.intent === 'sectionPrev' || d.intent === 'sectionNext') {
296
+ const by = d.intent === 'sectionNext' ? 1 : -1;
297
+ setEntityIdx((i) => Math.min(Math.max(i + by, 0), dataList.length - 1));
298
+ } else if (d.source === 'keyboard') {
299
+ return false;
300
+ } else {
301
+ const by = d.intent === 'pageNext' ? 0.1 : -0.1;
302
+ setZoom((z) => Math.min(2.5, Math.max(0.3, z + by)));
303
+ }
304
+ },
305
+ { ref: rootRef, enabled: !asPopup || isOpen },
306
+ );
307
+ useInputHints(
308
+ rootRef,
309
+ !asPopup || isOpen
310
+ ? [
311
+ ...(dataList.length > 1
312
+ ? [
313
+ {
314
+ intent: 'sectionPrev' as const,
315
+ label: t('printPreview.previousPage', 'Previous page'),
316
+ },
317
+ { intent: 'sectionNext' as const, label: t('printPreview.nextPage', 'Next page') },
318
+ ]
319
+ : []),
320
+ { intent: 'pageNext', label: t('printPreview.zoomIn', 'Zoom in') },
321
+ { intent: 'pagePrev', label: t('printPreview.zoomOut', 'Zoom out') },
322
+ ]
323
+ : [],
324
+ );
325
+
288
326
  if (asPopup && !isOpen) {
289
327
  return null;
290
328
  }
291
329
 
292
330
  const body = (
293
331
  <div
332
+ ref={rootRef}
294
333
  className={`${styles.root} ${theme === 'dark' ? styles.dark : ''} ${className ?? ''}`}
295
334
  style={style}
296
335
  >
@@ -303,8 +342,9 @@ export const NicePrintPreview: React.FC<NicePrintPreviewProps> = ({
303
342
  className={styles.navBtn}
304
343
  disabled={entityIdx === 0}
305
344
  onClick={() => setEntityIdx((i) => i - 1)}
345
+ aria-label={t('printPreview.previousPage', 'Previous page')}
306
346
  >
307
- ‹
347
+ <NiceIcon name="chevron-left" size={14} />
308
348
  </button>
309
349
  <span className={styles.navLabel}>
310
350
  {entityIdx + 1} / {dataList.length}
@@ -313,8 +353,9 @@ export const NicePrintPreview: React.FC<NicePrintPreviewProps> = ({
313
353
  className={styles.navBtn}
314
354
  disabled={entityIdx >= dataList.length - 1}
315
355
  onClick={() => setEntityIdx((i) => i + 1)}
356
+ aria-label={t('printPreview.nextPage', 'Next page')}
316
357
  >
317
- ›
358
+ <NiceIcon name="chevron-right" size={14} />
318
359
  </button>
319
360
  </>
320
361
  )}
@@ -324,19 +365,28 @@ export const NicePrintPreview: React.FC<NicePrintPreviewProps> = ({
324
365
  </div>
325
366
 
326
367
  <div className={styles.zoomBar}>
327
- <button className={styles.zoomBtn} onClick={() => setZoom((z) => Math.max(0.3, z - 0.1))}>
328
- −
368
+ <button
369
+ className={styles.zoomBtn}
370
+ onClick={() => setZoom((z) => Math.max(0.3, z - 0.1))}
371
+ aria-label={t('printPreview.zoomOut', 'Zoom out')}
372
+ >
373
+ <NiceIcon name="minus" size={14} />
329
374
  </button>
330
375
  <span className={styles.zoomLabel}>{Math.round(zoom * 100)}%</span>
331
- <button className={styles.zoomBtn} onClick={() => setZoom((z) => Math.min(2.5, z + 0.1))}>
332
- +
376
+ <button
377
+ className={styles.zoomBtn}
378
+ onClick={() => setZoom((z) => Math.min(2.5, z + 0.1))}
379
+ aria-label={t('printPreview.zoomIn', 'Zoom in')}
380
+ >
381
+ <NiceIcon name="plus" size={14} />
333
382
  </button>
334
383
  <button
335
384
  className={styles.zoomBtn}
336
385
  onClick={() => setZoom(0.8)}
337
386
  title={t('printing.fit', 'Fit')}
387
+ aria-label={t('printing.fit', 'Fit')}
338
388
  >
339
- ⊡
389
+ <NiceIcon name="expand" size={14} />
340
390
  </button>
341
391
  </div>
342
392
 
@@ -405,8 +455,10 @@ export const NicePrintPreview: React.FC<NicePrintPreviewProps> = ({
405
455
  role="presentation"
406
456
  >
407
457
  <div
458
+ ref={dialogRef}
408
459
  role="dialog"
409
460
  aria-modal="true"
461
+ aria-label={template.name}
410
462
  style={{
411
463
  background: 'var(--nice-bg-elevated, var(--nice-bg-primary, #ffffff))',
412
464
  borderRadius: 12,
@@ -1,5 +1,4 @@
1
- /* eslint-disable jsx-a11y/click-events-have-key-events -- modal overlay backdrops (Esc-on-parent provides keyboard close), selection rows in .map() accompanied by visible cursor:pointer styling and sibling buttons; full keyboard migration via useAccessibleClick + extracted subcomponents tracked in TODO_1.0.10 A2.3 */
2
- import { NiceIcon, useNiceTranslation } from '@nice2dev/ui';
1
+ import { clickOnActivationKey, NiceIcon, useNiceTranslation } from '@nice2dev/ui';
3
2
  import React, { useState } from 'react';
4
3
 
5
4
  import type {
@@ -144,6 +143,9 @@ export const NiceTemplateBrowser: React.FC<NiceTemplateBrowserProps> = ({
144
143
  <div className={styles.grid}>
145
144
  {filtered.map((tpl) => (
146
145
  <div
146
+ role={onSelect ? 'button' : undefined}
147
+ tabIndex={onSelect ? 0 : undefined}
148
+ onKeyDown={onSelect ? clickOnActivationKey : undefined}
147
149
  key={tpl.id}
148
150
  className={`${styles.card} ${selectedId === tpl.id ? styles.cardSelected : ''}`}
149
151
  onClick={() => onSelect?.(tpl)}
@@ -1,5 +1,4 @@
1
- /* eslint-disable jsx-a11y/click-events-have-key-events -- modal overlay backdrops (Esc-on-parent provides keyboard close), selection rows in .map() accompanied by visible cursor:pointer styling and sibling buttons; full keyboard migration via useAccessibleClick + extracted subcomponents tracked in TODO_1.0.10 A2.3 */
2
- import { NiceAppShell, NiceIcon, useNiceTranslation } from '@nice2dev/ui';
1
+ import { clickOnActivationKey, NiceAppShell, NiceIcon, useNiceTranslation } from '@nice2dev/ui';
3
2
  import type { NiceTranslateFn } from '@nice2dev/ui';
4
3
  import React, {
5
4
  useState,
@@ -331,7 +330,12 @@ const FieldElement: React.FC<FieldElementProps> = ({
331
330
  };
332
331
 
333
332
  return (
334
- <div style={baseStyle} onClick={onClick} onMouseDown={readOnly ? undefined : onDragStart}>
333
+ <div
334
+ role="presentation"
335
+ style={baseStyle}
336
+ onClick={onClick}
337
+ onMouseDown={readOnly ? undefined : onDragStart}
338
+ >
335
339
  {isLine ? (
336
340
  <div
337
341
  style={{
@@ -900,6 +904,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
900
904
  <div className={styles.fieldList}>
901
905
  {(filteredFields ?? []).map((f) => (
902
906
  <div
907
+ role="button"
908
+ tabIndex={0}
909
+ onKeyDown={clickOnActivationKey}
903
910
  key={f.path}
904
911
  className={styles.fieldItem}
905
912
  draggable
@@ -954,6 +961,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
954
961
  .reverse()
955
962
  .map((f) => (
956
963
  <div
964
+ role="button"
965
+ tabIndex={0}
966
+ onKeyDown={clickOnActivationKey}
957
967
  key={f.id}
958
968
  className={`${styles.layerItem} ${selectedIds.has(f.id) ? styles.layerItemSelected : ''}`}
959
969
  onClick={() => onSelectField(f.id)}
@@ -2459,7 +2469,11 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2459
2469
  }
2460
2470
  >
2461
2471
  {/* Canvas area */}
2462
- <div className={styles.canvasArea} onClick={() => setSelectedIds(new Set())}>
2472
+ <div
2473
+ role="presentation"
2474
+ className={styles.canvasArea}
2475
+ onClick={() => setSelectedIds(new Set())}
2476
+ >
2463
2477
  {/* Test data bar */}
2464
2478
  {Array.isArray(sampleData) && sampleData.length > 0 && (
2465
2479
  <div className={styles.testDataBar}>
@@ -2490,7 +2504,11 @@ export const NiceTemplateEditor: React.FC<NiceTemplateEditorProps> = ({
2490
2504
  )}
2491
2505
 
2492
2506
  {/* Scrollable paper area */}
2493
- <div className={styles.paperScroll} onClick={(e) => e.stopPropagation()}>
2507
+ <div
2508
+ role="presentation"
2509
+ className={styles.paperScroll}
2510
+ onClick={(e) => e.stopPropagation()}
2511
+ >
2494
2512
  <div className={styles.paperShadow} style={{ width: paperWPx, minHeight: paperHPx }}>
2495
2513
  {/* Margin guides */}
2496
2514
  <div