@oslokommune/punkt-react 16.14.0 → 16.15.0

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 (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{dialog-polyfill.esm-1hPr0gl9-EorOF9Wq.js → dialog-polyfill.esm-BmVHGRTX-CuhXqEqJ.js} +1 -1
  3. package/dist/index.d.ts +50 -47
  4. package/dist/punkt-react.es.js +4482 -3014
  5. package/dist/punkt-react.umd.js +471 -76
  6. package/dist/shared-types/fileupload.d.ts +73 -0
  7. package/dist/shared-types/index.d.ts +2 -0
  8. package/dist/shared-utils/fileupload/announcements.d.ts +11 -0
  9. package/dist/shared-utils/fileupload/filename.d.ts +13 -0
  10. package/dist/shared-utils/fileupload/focus-trap.d.ts +12 -0
  11. package/dist/shared-utils/fileupload/formats.d.ts +30 -0
  12. package/dist/shared-utils/fileupload/index.d.ts +15 -0
  13. package/dist/shared-utils/fileupload/navigation.d.ts +5 -0
  14. package/dist/shared-utils/fileupload/size.d.ts +12 -0
  15. package/dist/shared-utils/fileupload/transfers.d.ts +17 -0
  16. package/dist/shared-utils/fileupload/truncate.d.ts +13 -0
  17. package/dist/shared-utils/fileupload/validation.d.ts +24 -0
  18. package/package.json +4 -4
  19. package/src/components/fileupload/DropZone.tsx +35 -22
  20. package/src/components/fileupload/FileUpload.test.tsx +165 -10
  21. package/src/components/fileupload/FileUpload.tsx +116 -184
  22. package/src/components/fileupload/QueueDisplay.test.tsx +51 -8
  23. package/src/components/fileupload/QueueDisplay.tsx +71 -99
  24. package/src/components/fileupload/QueueItemContent.tsx +120 -135
  25. package/src/components/fileupload/Subcomponents.tsx +175 -144
  26. package/src/components/fileupload/Truncate.test.tsx +65 -207
  27. package/src/components/fileupload/Truncate.tsx +24 -120
  28. package/src/components/fileupload/extensions/Comments.tsx +94 -106
  29. package/src/components/fileupload/extensions/Remove.tsx +3 -5
  30. package/src/components/fileupload/extensions/Rename.tsx +45 -42
  31. package/src/components/fileupload/hooks/index.ts +1 -0
  32. package/src/components/fileupload/hooks/useFileAttributes.ts +37 -29
  33. package/src/components/fileupload/hooks/useImagePreview.ts +3 -7
  34. package/src/components/fileupload/hooks/useOperationState.ts +29 -8
  35. package/src/components/fileupload/hooks/useRequiredFormValidation.ts +43 -0
  36. package/src/components/fileupload/hooks.ts +1 -0
  37. package/src/components/fileupload/types.ts +49 -32
  38. package/src/components/fileupload/utils.ts +3 -54
  39. package/src/components/modal/Modal.tsx +27 -15
  40. package/src/components/types.ts +1 -1
  41. package/src/components/fileupload/texts.ts +0 -20
@@ -1,5 +1,13 @@
1
1
  import { createContext, ReactElement, ReactNode } from 'react'
2
2
 
3
+ import type {
4
+ IFileItem,
5
+ IFileTransfer,
6
+ IFileValidateDetail,
7
+ TFileUploadStrategy,
8
+ TTransferProgress,
9
+ } from 'shared-types'
10
+
3
11
  import { uuidish } from '../../utils/stringutils'
4
12
 
5
13
  /** Unique identifier assigned to each selected file. */
@@ -11,11 +19,13 @@ export type TFileId = string
11
19
  * Notes:
12
20
  * - A `fileId` is generated automatically if omitted.
13
21
  * - `attributes.targetFilename` is the filename shown in the queue and may be updated by extensions (e.g. rename).
22
+ *
23
+ * Implements the shared `IFileItem` type from `shared-types`.
14
24
  */
15
- export class FileItem implements FileItem {
25
+ export class FileItem implements IFileItem {
16
26
  fileId: TFileId
17
27
  file: File
18
- attributes: Record<string, any> = {}
28
+ attributes: { targetFilename: string } & Record<string, unknown>
19
29
 
20
30
  constructor(file: File, fileId?: TFileId) {
21
31
  this.fileId = fileId || uuidish()
@@ -33,51 +43,58 @@ export const PktFileUploadContext = createContext<TPktFileUploadContext>({} as T
33
43
  /** The value type for `PktFileUpload` (`value` / `defaultValue`). */
34
44
  export type TFileItemList = Array<FileItem>
35
45
 
36
- /**
37
- * Transfer status for a file when using `uploadStrategy="custom"`.
38
- *
39
- * - `progress`: `0..1` during upload, or a state (`queued`, `done`, `error`, `canceled`)
40
- * - `showProgress`: if false, UI uses indeterminate "Laster opp..." style instead of progress bar
41
- * - `lastProgress`: used to render a "failed at X%" bar when an upload errors
42
- */
43
- export type TFileTransfer = {
44
- fileId: TFileId
45
- progress: number | 'done' | 'error' | 'canceled' | 'queued' // i tilfelle number: 0-1
46
- errorMessage?: string
47
- showProgress?: boolean // true = show progress bar/file size, false = show "Laster opp..."/just error message
48
- lastProgress?: number // Store progress value when error occurs (0-1)
49
- }
46
+ /** Transfer status for a file when using `uploadStrategy="custom"` — aliased from `shared-types`. */
47
+ export type TFileTransfer = IFileTransfer
50
48
 
51
49
  /** Upload mode: `form` posts the files on submit, `custom` posts file IDs and uploads separately. */
52
- export type TUploadStrategy = 'custom' | 'form'
50
+ export type TUploadStrategy = TFileUploadStrategy
51
+
52
+ /** Detail payload passed to the React `onFileValidate` escape hatch. */
53
+ export type TFileValidateDetail = IFileValidateDetail
54
+
55
+ /** Re-export for parity with Lit types. */
56
+ export type { TTransferProgress }
57
+
53
58
  export type TFileAttribute<T> = {
54
59
  get: (fileId: TFileId) => T | undefined
55
60
  set: (fileId: TFileId, attributeValue: T | undefined) => void
56
61
  }
57
62
  export type TFileAttributes = <T>(attributeName: string) => TFileAttribute<T>
58
63
 
64
+ /**
65
+ * Context passed to queue-item operations. Mirrors the Lit
66
+ * `TQueueOperationContext` so operations authored for one runtime are
67
+ * trivial to port to the other.
68
+ */
69
+ export type TQueueOperationContext = {
70
+ file: FileItem
71
+ inputName: string
72
+ disabled: boolean
73
+ isActive: boolean
74
+ activate: () => void
75
+ close: () => void
76
+ getAttribute: <T>(name: string) => T | undefined
77
+ setAttribute: (name: string, value: unknown) => void
78
+ }
79
+
80
+ export type TQueueOperationLabel = string | ((fileItem: FileItem) => string)
81
+
59
82
  /**
60
83
  * An operation that can be attached to a queue item (rename, comment, remove, etc).
61
84
  *
62
- * An operation may:
63
- * - be a simple action (`onClick`)
64
- * - render inline UI (e.g. rename)
65
- * - render extended UI (e.g. comments)
66
- * - render hidden inputs for form submission (`renderHidden`)
85
+ * Must carry a stable `id: string` — symbols are no longer supported.
67
86
  */
68
87
  export type TQueueItemOperation = {
69
- title: string | ((fileItem: FileItem) => string)
70
- ariaLabel?: string | ((fileItem: FileItem) => string)
71
- onClick?: (transferItem: FileItem) => void
72
- renderInlineUI?: (fileItem: FileItem, closeOperationUi: () => void) => ReactNode
73
- renderExtendedUI?: (fileItem: FileItem, closeOperationUi: () => void) => ReactNode
74
- renderContent?: (fileItem: FileItem, activateOperation?: () => void, isOperationActive?: boolean) => ReactNode
75
- renderHidden?: (fileItem: FileItem) => ReactNode
76
- symbol: symbol
88
+ id: string
89
+ title: TQueueOperationLabel
90
+ ariaLabel?: TQueueOperationLabel
91
+ onClick?: (context: TQueueOperationContext) => void
92
+ renderInlineUI?: (context: TQueueOperationContext) => ReactNode
93
+ renderExtendedUI?: (context: TQueueOperationContext) => ReactNode
94
+ renderContent?: (context: TQueueOperationContext) => ReactNode
95
+ renderHidden?: (context: TQueueOperationContext) => ReactNode
77
96
  }
78
97
 
79
- /** Factory that produces a queue item operation given access to file attributes. */
80
- export type TQueueItemExtension = (attributes: TFileAttributes) => TQueueItemOperation
81
98
  export type TFileAndTransfer = FileItem &
82
99
  Pick<TFileTransfer, 'progress' | 'errorMessage' | 'showProgress' | 'lastProgress'>
83
100
  export type TTransferItemInProgress = TFileAndTransfer & { progress: number }
@@ -1,56 +1,5 @@
1
1
  /**
2
- * Utility functions for the FileUpload component
2
+ * Re-export of the shared fileupload utilities.
3
+ * Kept as a file entry so existing `import { parseFileSize } from './utils'` paths keep working.
3
4
  */
4
-
5
- /**
6
- * Parse a file size string (e.g. "5MB", "500KB") to bytes.
7
- * Also accepts raw numbers (returned as-is).
8
- *
9
- * @example
10
- * parseFileSize("5MB") // 5242880
11
- * parseFileSize("500KB") // 512000
12
- * parseFileSize("1GB") // 1073741824
13
- * parseFileSize(1024) // 1024
14
- */
15
- export const parseFileSize = (size: string | number): number => {
16
- if (typeof size === 'number') return size
17
-
18
- const match = size.trim().match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)$/i)
19
- if (!match) {
20
- console.warn(
21
- `PktFileUpload: Invalid maxFileSize format "${size}". Use format like "5MB", "500KB", or a number in bytes.`,
22
- )
23
- return 0
24
- }
25
-
26
- const value = parseFloat(match[1])
27
- const unit = match[2].toUpperCase()
28
-
29
- switch (unit) {
30
- case 'B':
31
- return value
32
- case 'KB':
33
- return value * 1024
34
- case 'MB':
35
- return value * 1024 * 1024
36
- case 'GB':
37
- return value * 1024 * 1024 * 1024
38
- default:
39
- return value
40
- }
41
- }
42
-
43
- /**
44
- * Format bytes to a human-readable string (e.g. "5 MB", "500 KB").
45
- *
46
- * @example
47
- * formatFileSize(5242880) // "5 MB"
48
- * formatFileSize(512000) // "500 KB"
49
- * formatFileSize(500) // "500 B"
50
- */
51
- export const formatFileSize = (bytes: number): string => {
52
- if (bytes < 1024) return `${bytes} B`
53
- if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`
54
- if (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))} MB`
55
- return `${Math.round(bytes / (1024 * 1024 * 1024))} GB`
56
- }
5
+ export { parseFileSize, formatFileSize } from 'shared-utils/fileupload'
@@ -5,6 +5,7 @@ import {
5
5
  ForwardedRef,
6
6
  forwardRef,
7
7
  HTMLAttributes,
8
+ MutableRefObject,
8
9
  ReactNode,
9
10
  Ref,
10
11
  useCallback,
@@ -51,18 +52,29 @@ export const PktModal = forwardRef(
51
52
  }: IPktModal,
52
53
  ref: ForwardedRef<HTMLDialogElement>,
53
54
  ) => {
54
- const internalRef = useRef<HTMLDialogElement>(null)
55
- const dialogRef = (ref as React.RefObject<HTMLDialogElement>) || internalRef
55
+ const internalRef = useRef<HTMLDialogElement | null>(null)
56
+
57
+ // Merge consumer ref + internal ref so we always have a working DOM handle
58
+ // for our own logic, regardless of whether the consumer passes a callback ref
59
+ // or an object ref (or nothing).
60
+ const setDialogRef = useCallback(
61
+ (node: HTMLDialogElement | null) => {
62
+ internalRef.current = node
63
+ if (typeof ref === 'function') ref(node)
64
+ else if (ref) (ref as MutableRefObject<HTMLDialogElement | null>).current = node
65
+ },
66
+ [ref],
67
+ )
56
68
 
57
69
  const close = useCallback(() => {
58
- if (!dialogRef.current?.open) return
59
- dialogRef.current?.close()
70
+ if (!internalRef.current?.open) return
71
+ internalRef.current.close()
60
72
  onClose?.()
61
- }, [dialogRef, onClose])
73
+ }, [onClose])
62
74
 
63
75
  // Synkroniser scroll-lock med dialogens faktiske åpen/lukket-tilstand
64
76
  useEffect(() => {
65
- const dialog = dialogRef.current
77
+ const dialog = internalRef.current
66
78
  if (!dialog) return
67
79
 
68
80
  const observer = new MutationObserver(() => {
@@ -74,28 +86,28 @@ export const PktModal = forwardRef(
74
86
  })
75
87
  observer.observe(dialog, { attributes: true, attributeFilter: ['open'] })
76
88
  return () => observer.disconnect()
77
- }, [dialogRef])
89
+ }, [])
78
90
 
79
91
  // Deklarativ åpne/lukke via open-prop
80
92
  useEffect(() => {
81
93
  if (open) {
82
- if (!dialogRef.current?.open) {
83
- dialogRef.current?.showModal()
94
+ if (!internalRef.current?.open) {
95
+ internalRef.current?.showModal()
84
96
  }
85
97
  } else {
86
- if (dialogRef.current?.open) {
87
- dialogRef.current?.close()
98
+ if (internalRef.current?.open) {
99
+ internalRef.current.close()
88
100
  }
89
101
  }
90
- }, [open, dialogRef])
102
+ }, [open])
91
103
 
92
104
  const handleBackdropClick = useCallback(
93
105
  (event: React.MouseEvent<HTMLDialogElement>) => {
94
- if (closeOnBackdropClick && event.target === dialogRef.current) {
106
+ if (closeOnBackdropClick && event.target === internalRef.current) {
95
107
  close()
96
108
  }
97
109
  },
98
- [closeOnBackdropClick, dialogRef, close],
110
+ [closeOnBackdropClick, close],
99
111
  )
100
112
 
101
113
  const handleNativeClose = useCallback(() => {
@@ -122,7 +134,7 @@ export const PktModal = forwardRef(
122
134
  <dialog
123
135
  {...props}
124
136
  className={classes}
125
- ref={dialogRef}
137
+ ref={setDialogRef}
126
138
  aria-labelledby={headingText ? 'pkt-modal__headingText' : undefined}
127
139
  aria-describedby="pkt-modal__content"
128
140
  onClick={handleBackdropClick}
@@ -7,8 +7,8 @@ export type {
7
7
  TFileItemList,
8
8
  TFileTransfer,
9
9
  TItemRenderer,
10
- TQueueItemExtension,
11
10
  TQueueItemOperation,
11
+ TQueueOperationContext,
12
12
  TTransferItemInProgress,
13
13
  TUploadStrategy,
14
14
  } from './fileupload/types'
@@ -1,20 +0,0 @@
1
- export const uiMultipleTexts = {
2
- dropFilesHere: 'Slipp filene her ...',
3
- selectOrDragFiles: 'Dra filer hit for å laste dem opp eller ',
4
- chooseFiles: 'velg filer',
5
- }
6
- export const uiSingleTexts: typeof uiMultipleTexts = {
7
- dropFilesHere: 'Slipp filen her ...',
8
- selectOrDragFiles: 'Dra en fil for å laste den opp eller ',
9
- chooseFiles: 'velg en fil',
10
- }
11
- export const uiThumbnailMultipleTexts: typeof uiMultipleTexts = {
12
- dropFilesHere: 'Slipp bildene her ...',
13
- selectOrDragFiles: 'Dra bilder hit for å laste dem opp eller ',
14
- chooseFiles: 'velg fra kamerarull',
15
- }
16
- export const uiThumbnailSingleTexts: typeof uiMultipleTexts = {
17
- dropFilesHere: 'Slipp bildet her ...',
18
- selectOrDragFiles: 'Dra et bilde for å laste det opp eller ',
19
- chooseFiles: 'velg fra kamerarull',
20
- }