@pancake-pos/ui-extensions 0.0.0-stage → 2026.1.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.
- package/README.md +15 -2
- package/dist/elements.generated.d.ts +419 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +12 -0
- package/dist/page.d.ts +10 -0
- package/dist/page.js +332 -0
- package/dist/types.d.ts +272 -0
- package/package.json +40 -4
package/README.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pancake-pos/ui-extensions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SDK for Pancake POS apps: types for the global `pos` object and `pos-*` elements, `defineExtension`, and `connect()` for iframe pages.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// tsconfig.json: "types": ["@pancake-pos/ui-extensions"]
|
|
7
|
+
import { defineExtension } from '@pancake-pos/ui-extensions';
|
|
8
|
+
|
|
9
|
+
export default defineExtension('order.details.action', async ({ record }) => {
|
|
10
|
+
return { message: `Order ${record.system_id}` };
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Docs: https://pos.pages.fm/developers/apps/en (English), https://pos.pages.fm/developers/apps (Tiếng Việt). For AI tools: https://pos.pages.fm/developers/apps/llms-full.txt.
|
|
15
|
+
|
|
16
|
+
Part of Pancake POS Apps. Use is limited to building apps for Pancake POS.
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
export declare const API_VERSION = "2026-01";
|
|
2
|
+
/** Sự kiện của element pos-*: giá trị nằm trong `detail`. */
|
|
3
|
+
export interface PosEvent<D> {
|
|
4
|
+
readonly type: string;
|
|
5
|
+
readonly detail: D;
|
|
6
|
+
readonly target: unknown;
|
|
7
|
+
readonly currentTarget: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** Thuộc tính chung của mọi element pos-* trong JSX. */
|
|
10
|
+
export interface PosBaseAttributes {
|
|
11
|
+
slot?: string;
|
|
12
|
+
key?: string | number;
|
|
13
|
+
ref?: unknown;
|
|
14
|
+
children?: unknown;
|
|
15
|
+
}
|
|
16
|
+
/** Thuộc tính của `<pos-badge>`. */
|
|
17
|
+
export interface PosBadgeProps {
|
|
18
|
+
tone?: "default" | "info" | "success" | "warning" | "critical";
|
|
19
|
+
}
|
|
20
|
+
/** Sự kiện của `<pos-badge>` → kiểu `event.detail`. */
|
|
21
|
+
export interface PosBadgeEvents {
|
|
22
|
+
}
|
|
23
|
+
export interface PosBadgeAttributes extends PosBadgeProps, PosBaseAttributes {
|
|
24
|
+
}
|
|
25
|
+
export interface PosBadgeElement extends PosBadgeProps {
|
|
26
|
+
scrollIntoView(): void;
|
|
27
|
+
}
|
|
28
|
+
/** Thuộc tính của `<pos-banner>`. */
|
|
29
|
+
export interface PosBannerProps {
|
|
30
|
+
tone?: "info" | "success" | "warning" | "critical";
|
|
31
|
+
heading?: string;
|
|
32
|
+
dismissible?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Sự kiện của `<pos-banner>` → kiểu `event.detail`. */
|
|
35
|
+
export interface PosBannerEvents {
|
|
36
|
+
dismiss: null;
|
|
37
|
+
}
|
|
38
|
+
export interface PosBannerAttributes extends PosBannerProps, PosBaseAttributes {
|
|
39
|
+
onDismiss?: (event: PosEvent<PosBannerEvents["dismiss"]>) => void;
|
|
40
|
+
}
|
|
41
|
+
export interface PosBannerElement extends PosBannerProps {
|
|
42
|
+
scrollIntoView(): void;
|
|
43
|
+
}
|
|
44
|
+
/** Thuộc tính của `<pos-button>`. */
|
|
45
|
+
export interface PosButtonProps {
|
|
46
|
+
variant?: "primary" | "secondary" | "tertiary" | "danger";
|
|
47
|
+
size?: "small" | "medium";
|
|
48
|
+
loading?: boolean;
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Sự kiện của `<pos-button>` → kiểu `event.detail`. */
|
|
52
|
+
export interface PosButtonEvents {
|
|
53
|
+
click: null;
|
|
54
|
+
}
|
|
55
|
+
export interface PosButtonAttributes extends PosButtonProps, PosBaseAttributes {
|
|
56
|
+
onClick?: (event: PosEvent<PosButtonEvents["click"]>) => void;
|
|
57
|
+
}
|
|
58
|
+
export interface PosButtonElement extends PosButtonProps {
|
|
59
|
+
focus(): void;
|
|
60
|
+
scrollIntoView(): void;
|
|
61
|
+
}
|
|
62
|
+
/** Thuộc tính của `<pos-checkbox>`. */
|
|
63
|
+
export interface PosCheckboxProps {
|
|
64
|
+
label?: string;
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
required?: boolean;
|
|
67
|
+
error?: string;
|
|
68
|
+
help?: string;
|
|
69
|
+
checked?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Sự kiện của `<pos-checkbox>` → kiểu `event.detail`. */
|
|
72
|
+
export interface PosCheckboxEvents {
|
|
73
|
+
change: boolean;
|
|
74
|
+
blur: null;
|
|
75
|
+
}
|
|
76
|
+
export interface PosCheckboxAttributes extends PosCheckboxProps, PosBaseAttributes {
|
|
77
|
+
onChange?: (event: PosEvent<PosCheckboxEvents["change"]>) => void;
|
|
78
|
+
onBlur?: (event: PosEvent<PosCheckboxEvents["blur"]>) => void;
|
|
79
|
+
}
|
|
80
|
+
export interface PosCheckboxElement extends PosCheckboxProps {
|
|
81
|
+
focus(): void;
|
|
82
|
+
scrollIntoView(): void;
|
|
83
|
+
}
|
|
84
|
+
/** Thuộc tính của `<pos-date-picker>`. */
|
|
85
|
+
export interface PosDatePickerProps {
|
|
86
|
+
label?: string;
|
|
87
|
+
disabled?: boolean;
|
|
88
|
+
required?: boolean;
|
|
89
|
+
error?: string;
|
|
90
|
+
help?: string;
|
|
91
|
+
value?: string | null;
|
|
92
|
+
min?: string;
|
|
93
|
+
max?: string;
|
|
94
|
+
}
|
|
95
|
+
/** Sự kiện của `<pos-date-picker>` → kiểu `event.detail`. */
|
|
96
|
+
export interface PosDatePickerEvents {
|
|
97
|
+
change: string | null;
|
|
98
|
+
blur: null;
|
|
99
|
+
}
|
|
100
|
+
export interface PosDatePickerAttributes extends PosDatePickerProps, PosBaseAttributes {
|
|
101
|
+
onChange?: (event: PosEvent<PosDatePickerEvents["change"]>) => void;
|
|
102
|
+
onBlur?: (event: PosEvent<PosDatePickerEvents["blur"]>) => void;
|
|
103
|
+
}
|
|
104
|
+
export interface PosDatePickerElement extends PosDatePickerProps {
|
|
105
|
+
focus(): void;
|
|
106
|
+
scrollIntoView(): void;
|
|
107
|
+
}
|
|
108
|
+
/** Thuộc tính của `<pos-divider>`. */
|
|
109
|
+
export interface PosDividerProps {
|
|
110
|
+
}
|
|
111
|
+
/** Sự kiện của `<pos-divider>` → kiểu `event.detail`. */
|
|
112
|
+
export interface PosDividerEvents {
|
|
113
|
+
}
|
|
114
|
+
export interface PosDividerAttributes extends PosDividerProps, PosBaseAttributes {
|
|
115
|
+
}
|
|
116
|
+
export interface PosDividerElement extends PosDividerProps {
|
|
117
|
+
scrollIntoView(): void;
|
|
118
|
+
}
|
|
119
|
+
/** Thuộc tính của `<pos-grid>`. */
|
|
120
|
+
export interface PosGridProps {
|
|
121
|
+
columns?: number;
|
|
122
|
+
gap?: "none" | "small" | "medium" | "large";
|
|
123
|
+
}
|
|
124
|
+
/** Sự kiện của `<pos-grid>` → kiểu `event.detail`. */
|
|
125
|
+
export interface PosGridEvents {
|
|
126
|
+
}
|
|
127
|
+
export interface PosGridAttributes extends PosGridProps, PosBaseAttributes {
|
|
128
|
+
}
|
|
129
|
+
export interface PosGridElement extends PosGridProps {
|
|
130
|
+
scrollIntoView(): void;
|
|
131
|
+
}
|
|
132
|
+
/** Thuộc tính của `<pos-heading>`. */
|
|
133
|
+
export interface PosHeadingProps {
|
|
134
|
+
level?: number;
|
|
135
|
+
}
|
|
136
|
+
/** Sự kiện của `<pos-heading>` → kiểu `event.detail`. */
|
|
137
|
+
export interface PosHeadingEvents {
|
|
138
|
+
}
|
|
139
|
+
export interface PosHeadingAttributes extends PosHeadingProps, PosBaseAttributes {
|
|
140
|
+
}
|
|
141
|
+
export interface PosHeadingElement extends PosHeadingProps {
|
|
142
|
+
scrollIntoView(): void;
|
|
143
|
+
}
|
|
144
|
+
/** Thuộc tính của `<pos-image>`. */
|
|
145
|
+
export interface PosImageProps {
|
|
146
|
+
src?: string;
|
|
147
|
+
alt?: string;
|
|
148
|
+
width?: number;
|
|
149
|
+
height?: number;
|
|
150
|
+
fit?: "cover" | "contain";
|
|
151
|
+
}
|
|
152
|
+
/** Sự kiện của `<pos-image>` → kiểu `event.detail`. */
|
|
153
|
+
export interface PosImageEvents {
|
|
154
|
+
}
|
|
155
|
+
export interface PosImageAttributes extends PosImageProps, PosBaseAttributes {
|
|
156
|
+
}
|
|
157
|
+
export interface PosImageElement extends PosImageProps {
|
|
158
|
+
scrollIntoView(): void;
|
|
159
|
+
}
|
|
160
|
+
/** Thuộc tính của `<pos-link>`. */
|
|
161
|
+
export interface PosLinkProps {
|
|
162
|
+
href?: string;
|
|
163
|
+
}
|
|
164
|
+
/** Sự kiện của `<pos-link>` → kiểu `event.detail`. */
|
|
165
|
+
export interface PosLinkEvents {
|
|
166
|
+
click: null;
|
|
167
|
+
}
|
|
168
|
+
export interface PosLinkAttributes extends PosLinkProps, PosBaseAttributes {
|
|
169
|
+
onClick?: (event: PosEvent<PosLinkEvents["click"]>) => void;
|
|
170
|
+
}
|
|
171
|
+
export interface PosLinkElement extends PosLinkProps {
|
|
172
|
+
focus(): void;
|
|
173
|
+
scrollIntoView(): void;
|
|
174
|
+
}
|
|
175
|
+
/** Thuộc tính của `<pos-modal>`. */
|
|
176
|
+
export interface PosModalProps {
|
|
177
|
+
open?: boolean;
|
|
178
|
+
heading?: string;
|
|
179
|
+
}
|
|
180
|
+
/** Sự kiện của `<pos-modal>` → kiểu `event.detail`. */
|
|
181
|
+
export interface PosModalEvents {
|
|
182
|
+
close: null;
|
|
183
|
+
}
|
|
184
|
+
export interface PosModalAttributes extends PosModalProps, PosBaseAttributes {
|
|
185
|
+
onClose?: (event: PosEvent<PosModalEvents["close"]>) => void;
|
|
186
|
+
}
|
|
187
|
+
export interface PosModalElement extends PosModalProps {
|
|
188
|
+
scrollIntoView(): void;
|
|
189
|
+
}
|
|
190
|
+
/** Thuộc tính của `<pos-money>`. */
|
|
191
|
+
export interface PosMoneyProps {
|
|
192
|
+
value?: number;
|
|
193
|
+
currency?: string;
|
|
194
|
+
tone?: "default" | "subdued" | "success" | "warning" | "critical";
|
|
195
|
+
}
|
|
196
|
+
/** Sự kiện của `<pos-money>` → kiểu `event.detail`. */
|
|
197
|
+
export interface PosMoneyEvents {
|
|
198
|
+
}
|
|
199
|
+
export interface PosMoneyAttributes extends PosMoneyProps, PosBaseAttributes {
|
|
200
|
+
}
|
|
201
|
+
export interface PosMoneyElement extends PosMoneyProps {
|
|
202
|
+
scrollIntoView(): void;
|
|
203
|
+
}
|
|
204
|
+
/** Thuộc tính của `<pos-number-field>`. */
|
|
205
|
+
export interface PosNumberFieldProps {
|
|
206
|
+
label?: string;
|
|
207
|
+
disabled?: boolean;
|
|
208
|
+
required?: boolean;
|
|
209
|
+
error?: string;
|
|
210
|
+
help?: string;
|
|
211
|
+
value?: number | null;
|
|
212
|
+
min?: number;
|
|
213
|
+
max?: number;
|
|
214
|
+
step?: number;
|
|
215
|
+
suffix?: string;
|
|
216
|
+
}
|
|
217
|
+
/** Sự kiện của `<pos-number-field>` → kiểu `event.detail`. */
|
|
218
|
+
export interface PosNumberFieldEvents {
|
|
219
|
+
change: number | null;
|
|
220
|
+
blur: null;
|
|
221
|
+
}
|
|
222
|
+
export interface PosNumberFieldAttributes extends PosNumberFieldProps, PosBaseAttributes {
|
|
223
|
+
onChange?: (event: PosEvent<PosNumberFieldEvents["change"]>) => void;
|
|
224
|
+
onBlur?: (event: PosEvent<PosNumberFieldEvents["blur"]>) => void;
|
|
225
|
+
}
|
|
226
|
+
export interface PosNumberFieldElement extends PosNumberFieldProps {
|
|
227
|
+
focus(): void;
|
|
228
|
+
scrollIntoView(): void;
|
|
229
|
+
}
|
|
230
|
+
/** Thuộc tính của `<pos-section>`. */
|
|
231
|
+
export interface PosSectionProps {
|
|
232
|
+
heading?: string;
|
|
233
|
+
description?: string;
|
|
234
|
+
}
|
|
235
|
+
/** Sự kiện của `<pos-section>` → kiểu `event.detail`. */
|
|
236
|
+
export interface PosSectionEvents {
|
|
237
|
+
}
|
|
238
|
+
export interface PosSectionAttributes extends PosSectionProps, PosBaseAttributes {
|
|
239
|
+
}
|
|
240
|
+
export interface PosSectionElement extends PosSectionProps {
|
|
241
|
+
scrollIntoView(): void;
|
|
242
|
+
}
|
|
243
|
+
/** Thuộc tính của `<pos-select>`. */
|
|
244
|
+
export interface PosSelectProps {
|
|
245
|
+
label?: string;
|
|
246
|
+
disabled?: boolean;
|
|
247
|
+
required?: boolean;
|
|
248
|
+
error?: string;
|
|
249
|
+
help?: string;
|
|
250
|
+
options?: {
|
|
251
|
+
label: string;
|
|
252
|
+
value: string;
|
|
253
|
+
disabled?: boolean;
|
|
254
|
+
}[];
|
|
255
|
+
value?: string | string[];
|
|
256
|
+
multiple?: boolean;
|
|
257
|
+
placeholder?: string;
|
|
258
|
+
searchable?: boolean;
|
|
259
|
+
}
|
|
260
|
+
/** Sự kiện của `<pos-select>` → kiểu `event.detail`. */
|
|
261
|
+
export interface PosSelectEvents {
|
|
262
|
+
change: string | string[];
|
|
263
|
+
blur: null;
|
|
264
|
+
}
|
|
265
|
+
export interface PosSelectAttributes extends PosSelectProps, PosBaseAttributes {
|
|
266
|
+
onChange?: (event: PosEvent<PosSelectEvents["change"]>) => void;
|
|
267
|
+
onBlur?: (event: PosEvent<PosSelectEvents["blur"]>) => void;
|
|
268
|
+
}
|
|
269
|
+
export interface PosSelectElement extends PosSelectProps {
|
|
270
|
+
focus(): void;
|
|
271
|
+
scrollIntoView(): void;
|
|
272
|
+
}
|
|
273
|
+
/** Thuộc tính của `<pos-spinner>`. */
|
|
274
|
+
export interface PosSpinnerProps {
|
|
275
|
+
size?: "small" | "medium" | "large";
|
|
276
|
+
}
|
|
277
|
+
/** Sự kiện của `<pos-spinner>` → kiểu `event.detail`. */
|
|
278
|
+
export interface PosSpinnerEvents {
|
|
279
|
+
}
|
|
280
|
+
export interface PosSpinnerAttributes extends PosSpinnerProps, PosBaseAttributes {
|
|
281
|
+
}
|
|
282
|
+
export interface PosSpinnerElement extends PosSpinnerProps {
|
|
283
|
+
scrollIntoView(): void;
|
|
284
|
+
}
|
|
285
|
+
/** Thuộc tính của `<pos-stack>`. */
|
|
286
|
+
export interface PosStackProps {
|
|
287
|
+
direction?: "vertical" | "horizontal";
|
|
288
|
+
gap?: "none" | "small" | "medium" | "large";
|
|
289
|
+
align?: "start" | "center" | "end" | "stretch";
|
|
290
|
+
justify?: "start" | "center" | "end" | "space-between";
|
|
291
|
+
wrap?: boolean;
|
|
292
|
+
}
|
|
293
|
+
/** Sự kiện của `<pos-stack>` → kiểu `event.detail`. */
|
|
294
|
+
export interface PosStackEvents {
|
|
295
|
+
}
|
|
296
|
+
export interface PosStackAttributes extends PosStackProps, PosBaseAttributes {
|
|
297
|
+
}
|
|
298
|
+
export interface PosStackElement extends PosStackProps {
|
|
299
|
+
scrollIntoView(): void;
|
|
300
|
+
}
|
|
301
|
+
/** Thuộc tính của `<pos-table>`. */
|
|
302
|
+
export interface PosTableProps {
|
|
303
|
+
columns?: ({
|
|
304
|
+
key: string;
|
|
305
|
+
title: string;
|
|
306
|
+
align?: "left" | "center" | "right";
|
|
307
|
+
width?: number;
|
|
308
|
+
format?: "text" | "number" | "money" | "date" | "datetime";
|
|
309
|
+
})[];
|
|
310
|
+
rows?: (Record<string, string | number | boolean | null>)[];
|
|
311
|
+
rowKey?: string;
|
|
312
|
+
loading?: boolean;
|
|
313
|
+
pagination?: {
|
|
314
|
+
page: number;
|
|
315
|
+
pageSize: number;
|
|
316
|
+
total: number;
|
|
317
|
+
} | null;
|
|
318
|
+
emptyText?: string;
|
|
319
|
+
}
|
|
320
|
+
/** Sự kiện của `<pos-table>` → kiểu `event.detail`. */
|
|
321
|
+
export interface PosTableEvents {
|
|
322
|
+
pagechange: {
|
|
323
|
+
page: number;
|
|
324
|
+
pageSize: number;
|
|
325
|
+
};
|
|
326
|
+
rowclick: Record<string, string | number | boolean | null>;
|
|
327
|
+
}
|
|
328
|
+
export interface PosTableAttributes extends PosTableProps, PosBaseAttributes {
|
|
329
|
+
onPageChange?: (event: PosEvent<PosTableEvents["pagechange"]>) => void;
|
|
330
|
+
onRowClick?: (event: PosEvent<PosTableEvents["rowclick"]>) => void;
|
|
331
|
+
}
|
|
332
|
+
export interface PosTableElement extends PosTableProps {
|
|
333
|
+
scrollIntoView(): void;
|
|
334
|
+
}
|
|
335
|
+
/** Thuộc tính của `<pos-text>`. */
|
|
336
|
+
export interface PosTextProps {
|
|
337
|
+
tone?: "default" | "subdued" | "success" | "warning" | "critical";
|
|
338
|
+
size?: "small" | "medium" | "large";
|
|
339
|
+
weight?: "regular" | "semibold" | "bold";
|
|
340
|
+
}
|
|
341
|
+
/** Sự kiện của `<pos-text>` → kiểu `event.detail`. */
|
|
342
|
+
export interface PosTextEvents {
|
|
343
|
+
}
|
|
344
|
+
export interface PosTextAttributes extends PosTextProps, PosBaseAttributes {
|
|
345
|
+
}
|
|
346
|
+
export interface PosTextElement extends PosTextProps {
|
|
347
|
+
scrollIntoView(): void;
|
|
348
|
+
}
|
|
349
|
+
/** Thuộc tính của `<pos-text-field>`. */
|
|
350
|
+
export interface PosTextFieldProps {
|
|
351
|
+
label?: string;
|
|
352
|
+
disabled?: boolean;
|
|
353
|
+
required?: boolean;
|
|
354
|
+
error?: string;
|
|
355
|
+
help?: string;
|
|
356
|
+
value?: string;
|
|
357
|
+
placeholder?: string;
|
|
358
|
+
multiline?: boolean | number;
|
|
359
|
+
maxLength?: number;
|
|
360
|
+
}
|
|
361
|
+
/** Sự kiện của `<pos-text-field>` → kiểu `event.detail`. */
|
|
362
|
+
export interface PosTextFieldEvents {
|
|
363
|
+
change: string;
|
|
364
|
+
blur: null;
|
|
365
|
+
}
|
|
366
|
+
export interface PosTextFieldAttributes extends PosTextFieldProps, PosBaseAttributes {
|
|
367
|
+
onChange?: (event: PosEvent<PosTextFieldEvents["change"]>) => void;
|
|
368
|
+
onBlur?: (event: PosEvent<PosTextFieldEvents["blur"]>) => void;
|
|
369
|
+
}
|
|
370
|
+
export interface PosTextFieldElement extends PosTextFieldProps {
|
|
371
|
+
focus(): void;
|
|
372
|
+
scrollIntoView(): void;
|
|
373
|
+
}
|
|
374
|
+
/** Tên element → thuộc tính trong JSX. */
|
|
375
|
+
export interface PosIntrinsicElements {
|
|
376
|
+
"pos-badge": PosBadgeAttributes;
|
|
377
|
+
"pos-banner": PosBannerAttributes;
|
|
378
|
+
"pos-button": PosButtonAttributes;
|
|
379
|
+
"pos-checkbox": PosCheckboxAttributes;
|
|
380
|
+
"pos-date-picker": PosDatePickerAttributes;
|
|
381
|
+
"pos-divider": PosDividerAttributes;
|
|
382
|
+
"pos-grid": PosGridAttributes;
|
|
383
|
+
"pos-heading": PosHeadingAttributes;
|
|
384
|
+
"pos-image": PosImageAttributes;
|
|
385
|
+
"pos-link": PosLinkAttributes;
|
|
386
|
+
"pos-modal": PosModalAttributes;
|
|
387
|
+
"pos-money": PosMoneyAttributes;
|
|
388
|
+
"pos-number-field": PosNumberFieldAttributes;
|
|
389
|
+
"pos-section": PosSectionAttributes;
|
|
390
|
+
"pos-select": PosSelectAttributes;
|
|
391
|
+
"pos-spinner": PosSpinnerAttributes;
|
|
392
|
+
"pos-stack": PosStackAttributes;
|
|
393
|
+
"pos-table": PosTableAttributes;
|
|
394
|
+
"pos-text": PosTextAttributes;
|
|
395
|
+
"pos-text-field": PosTextFieldAttributes;
|
|
396
|
+
}
|
|
397
|
+
/** Tên element → element tạo bằng `document.createElement` (JS thuần). */
|
|
398
|
+
export interface PosHTMLElementTagNameMap {
|
|
399
|
+
"pos-badge": Omit<HTMLElement, keyof PosBadgeElement> & PosBadgeElement;
|
|
400
|
+
"pos-banner": Omit<HTMLElement, keyof PosBannerElement> & PosBannerElement;
|
|
401
|
+
"pos-button": Omit<HTMLElement, keyof PosButtonElement> & PosButtonElement;
|
|
402
|
+
"pos-checkbox": Omit<HTMLElement, keyof PosCheckboxElement> & PosCheckboxElement;
|
|
403
|
+
"pos-date-picker": Omit<HTMLElement, keyof PosDatePickerElement> & PosDatePickerElement;
|
|
404
|
+
"pos-divider": Omit<HTMLElement, keyof PosDividerElement> & PosDividerElement;
|
|
405
|
+
"pos-grid": Omit<HTMLElement, keyof PosGridElement> & PosGridElement;
|
|
406
|
+
"pos-heading": Omit<HTMLElement, keyof PosHeadingElement> & PosHeadingElement;
|
|
407
|
+
"pos-image": Omit<HTMLElement, keyof PosImageElement> & PosImageElement;
|
|
408
|
+
"pos-link": Omit<HTMLElement, keyof PosLinkElement> & PosLinkElement;
|
|
409
|
+
"pos-modal": Omit<HTMLElement, keyof PosModalElement> & PosModalElement;
|
|
410
|
+
"pos-money": Omit<HTMLElement, keyof PosMoneyElement> & PosMoneyElement;
|
|
411
|
+
"pos-number-field": Omit<HTMLElement, keyof PosNumberFieldElement> & PosNumberFieldElement;
|
|
412
|
+
"pos-section": Omit<HTMLElement, keyof PosSectionElement> & PosSectionElement;
|
|
413
|
+
"pos-select": Omit<HTMLElement, keyof PosSelectElement> & PosSelectElement;
|
|
414
|
+
"pos-spinner": Omit<HTMLElement, keyof PosSpinnerElement> & PosSpinnerElement;
|
|
415
|
+
"pos-stack": Omit<HTMLElement, keyof PosStackElement> & PosStackElement;
|
|
416
|
+
"pos-table": Omit<HTMLElement, keyof PosTableElement> & PosTableElement;
|
|
417
|
+
"pos-text": Omit<HTMLElement, keyof PosTextElement> & PosTextElement;
|
|
418
|
+
"pos-text-field": Omit<HTMLElement, keyof PosTextFieldElement> & PosTextFieldElement;
|
|
419
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PosHTMLElementTagNameMap, PosIntrinsicElements } from './elements.generated';
|
|
2
|
+
import type { DataOf, Pos, ResultOf, Target } from './types';
|
|
3
|
+
export * from './types';
|
|
4
|
+
export * from './elements.generated';
|
|
5
|
+
/**
|
|
6
|
+
* Giúp type của `data` và giá trị trả về khớp target. Trả lại đúng hàm truyền vào,
|
|
7
|
+
* nên tương đương `export default async function (data)` (tài liệu public `sdk` › defineExtension).
|
|
8
|
+
*/
|
|
9
|
+
export declare function defineExtension<T extends Target>(target: T, fn: (data: DataOf<T>) => ResultOf<T> | Promise<ResultOf<T>>): (data: DataOf<T>) => ResultOf<T> | Promise<ResultOf<T>>;
|
|
10
|
+
declare global {
|
|
11
|
+
/** Đối tượng POS trong worker của extension (tài liệu public `runtime-api`). */
|
|
12
|
+
const pos: Pos;
|
|
13
|
+
/** `document.createElement('pos-button')` trong JS thuần trả element có thuộc tính đúng kiểu. */
|
|
14
|
+
interface HTMLElementTagNameMap extends PosHTMLElementTagNameMap {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
declare module 'preact' {
|
|
18
|
+
namespace JSX {
|
|
19
|
+
interface IntrinsicElements extends PosIntrinsicElements {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/index.js
ADDED
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PosApiError as PosApiErrorType, PosPage, PosRuntimeError as PosRuntimeErrorType } from './types';
|
|
2
|
+
export type { PageData, PosPage } from './types';
|
|
3
|
+
export declare const PosApiError: new (status: number, message: string, code?: number | string, body?: unknown) => PosApiErrorType;
|
|
4
|
+
export declare const PosRuntimeError: new (code: string, message: string) => PosRuntimeErrorType;
|
|
5
|
+
export interface ConnectOptions {
|
|
6
|
+
/** Chờ POS trả lời (mặc định 10 giây); quá hạn thường do mở trang ngoài POS. */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Kết nối với POS. Gọi nhiều lần trả cùng một kết nối. */
|
|
10
|
+
export declare function connect(options?: ConnectOptions): Promise<PosPage>;
|
package/dist/page.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// ../app-protocol/src/protocol.ts
|
|
2
|
+
var PROTOCOL_VERSION = 1;
|
|
3
|
+
function extensionContext(kind, target) {
|
|
4
|
+
if (kind === "hook") return "hook";
|
|
5
|
+
if (kind === "headless") return "action-headless";
|
|
6
|
+
if (target === "app.page") return "page";
|
|
7
|
+
if (target.endsWith(".details.block")) return "block";
|
|
8
|
+
return "action-ui";
|
|
9
|
+
}
|
|
10
|
+
var AVAILABILITY = {
|
|
11
|
+
"toast.show": ["block", "action-headless", "action-ui", "page"],
|
|
12
|
+
"ui.confirm": ["block", "action-headless", "action-ui", "page"],
|
|
13
|
+
navigate: ["block", "action-headless", "action-ui", "page"],
|
|
14
|
+
refresh: ["block", "action-headless", "action-ui"],
|
|
15
|
+
close: ["action-ui"],
|
|
16
|
+
"session.token": ["block", "action-headless", "action-ui", "hook", "page"],
|
|
17
|
+
"element.focus": ["block", "action-ui", "page"],
|
|
18
|
+
"element.scrollIntoView": ["block", "action-ui", "page"],
|
|
19
|
+
"page.setTitle": ["page"],
|
|
20
|
+
"page.setPrimaryAction": ["page"],
|
|
21
|
+
"data-change": ["block", "action-ui", "page"]
|
|
22
|
+
};
|
|
23
|
+
function isAvailable(capability, context) {
|
|
24
|
+
return Object.prototype.hasOwnProperty.call(AVAILABILITY, capability) && AVAILABILITY[capability].includes(context);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ../../runtime/worker/pos.ts
|
|
28
|
+
var TOKEN_REFRESH_BEFORE_MS = 6e4;
|
|
29
|
+
var TOKEN_REFRESH_MIN_INTERVAL_MS = 3e4;
|
|
30
|
+
var TOAST_LIMIT = 3;
|
|
31
|
+
var TOAST_WINDOW_MS = 1e4;
|
|
32
|
+
var PosRuntimeError = class extends Error {
|
|
33
|
+
constructor(code, message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.code = code;
|
|
36
|
+
this.name = "PosRuntimeError";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var PosApiError = class extends Error {
|
|
40
|
+
constructor(status, message, code, body) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.status = status;
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.body = body;
|
|
45
|
+
this.name = "PosApiError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function buildQuery(query) {
|
|
49
|
+
if (!query) return "";
|
|
50
|
+
const params = new URLSearchParams();
|
|
51
|
+
for (const [key, value] of Object.entries(query)) {
|
|
52
|
+
if (value === void 0 || value === null) continue;
|
|
53
|
+
if (Array.isArray(value)) for (const item of value) params.append(key, String(item));
|
|
54
|
+
else params.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
55
|
+
}
|
|
56
|
+
const text = params.toString();
|
|
57
|
+
return text ? `?${text}` : "";
|
|
58
|
+
}
|
|
59
|
+
function createPos(deps) {
|
|
60
|
+
const { init } = deps;
|
|
61
|
+
const now = deps.now ?? Date.now;
|
|
62
|
+
const context = extensionContext(init.kind, init.target);
|
|
63
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
64
|
+
let data = init.data;
|
|
65
|
+
let token = { ...init.token };
|
|
66
|
+
let lastRefresh = 0;
|
|
67
|
+
let refreshing = null;
|
|
68
|
+
const toasts = [];
|
|
69
|
+
const require2 = (capability, name) => {
|
|
70
|
+
if (!isAvailable(capability, context)) throw new PosRuntimeError("unavailable", `${name} is not available in ${init.target}`);
|
|
71
|
+
};
|
|
72
|
+
const callHost = (method, args, name) => {
|
|
73
|
+
require2(method, name);
|
|
74
|
+
return deps.call(method, args);
|
|
75
|
+
};
|
|
76
|
+
const refreshToken = (force) => {
|
|
77
|
+
const expiresAt = Date.parse(token.expiresAt);
|
|
78
|
+
const stale = force || !Number.isFinite(expiresAt) || expiresAt - now() < TOKEN_REFRESH_BEFORE_MS;
|
|
79
|
+
if (!stale || !force && now() - lastRefresh < TOKEN_REFRESH_MIN_INTERVAL_MS) return Promise.resolve();
|
|
80
|
+
refreshing ?? (refreshing = deps.call("session.token", force ? [{ force: true }] : []).then((next) => {
|
|
81
|
+
const value = next;
|
|
82
|
+
if (typeof value?.token === "string" && typeof value.expiresAt === "string") token = { token: value.token, expiresAt: value.expiresAt };
|
|
83
|
+
lastRefresh = now();
|
|
84
|
+
}).finally(() => {
|
|
85
|
+
refreshing = null;
|
|
86
|
+
}));
|
|
87
|
+
return refreshing;
|
|
88
|
+
};
|
|
89
|
+
const request = async (options) => {
|
|
90
|
+
const method = String(options.method).toUpperCase();
|
|
91
|
+
if (!init.api.methods.includes(method)) throw new PosRuntimeError("method_not_allowed", `${method} is not in access.methods`);
|
|
92
|
+
const shopPath = `/shops/${init.shop.id}`;
|
|
93
|
+
const path = options.path;
|
|
94
|
+
const inShop = typeof path === "string" && (path === shopPath || path.startsWith(`${shopPath}/`) || path.startsWith(`${shopPath}?`));
|
|
95
|
+
if (!inShop || path.includes("..")) {
|
|
96
|
+
throw new PosRuntimeError("invalid_path", `path must start with ${shopPath}`);
|
|
97
|
+
}
|
|
98
|
+
const url = `${init.api.baseUrl}${options.path}${buildQuery(options.query)}`;
|
|
99
|
+
const send = async () => {
|
|
100
|
+
const headers = { Authorization: `Bearer ${token.token}`, Accept: "application/json" };
|
|
101
|
+
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
102
|
+
return deps.fetch(url, {
|
|
103
|
+
method,
|
|
104
|
+
headers,
|
|
105
|
+
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
106
|
+
...options.signal ? { signal: options.signal } : {}
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
await refreshToken(false);
|
|
110
|
+
let response = await send();
|
|
111
|
+
if (response.status === 401) {
|
|
112
|
+
await refreshToken(true);
|
|
113
|
+
response = await send();
|
|
114
|
+
}
|
|
115
|
+
const text = await response.text();
|
|
116
|
+
let body = text;
|
|
117
|
+
try {
|
|
118
|
+
body = text ? JSON.parse(text) : null;
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
const failed = typeof body === "object" && body !== null && body.success === false;
|
|
122
|
+
if (response.status >= 400 || failed) {
|
|
123
|
+
const info = typeof body === "object" && body !== null ? body : {};
|
|
124
|
+
const message = typeof info.message === "string" ? info.message : `Open API request failed with status ${response.status}`;
|
|
125
|
+
const code = typeof info.error_code === "number" || typeof info.error_code === "string" ? info.error_code : void 0;
|
|
126
|
+
throw new PosApiError(response.status, message, code, body);
|
|
127
|
+
}
|
|
128
|
+
return body;
|
|
129
|
+
};
|
|
130
|
+
const shortcut = (method) => (path, options = {}) => request({ method, path, ...options });
|
|
131
|
+
const withBody = (method) => (path, body, options = {}) => request({ method, path, body, ...options });
|
|
132
|
+
const pos = {
|
|
133
|
+
apiVersion: init.apiVersion,
|
|
134
|
+
target: init.target,
|
|
135
|
+
extensionId: init.extensionId,
|
|
136
|
+
app: Object.freeze({ ...init.app }),
|
|
137
|
+
shop: Object.freeze({ ...init.shop }),
|
|
138
|
+
user: Object.freeze({ ...init.user }),
|
|
139
|
+
get data() {
|
|
140
|
+
return data;
|
|
141
|
+
},
|
|
142
|
+
api: Object.freeze({
|
|
143
|
+
baseUrl: init.api.baseUrl,
|
|
144
|
+
request,
|
|
145
|
+
get: shortcut("GET"),
|
|
146
|
+
delete: shortcut("DELETE"),
|
|
147
|
+
post: withBody("POST"),
|
|
148
|
+
put: withBody("PUT")
|
|
149
|
+
}),
|
|
150
|
+
session: Object.freeze({
|
|
151
|
+
async getToken() {
|
|
152
|
+
await refreshToken(false);
|
|
153
|
+
return { ...token };
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
toast: Object.freeze({
|
|
157
|
+
show(message, options = {}) {
|
|
158
|
+
require2("toast.show", "pos.toast.show");
|
|
159
|
+
const time = now();
|
|
160
|
+
while (toasts.length && time - toasts[0] > TOAST_WINDOW_MS) toasts.shift();
|
|
161
|
+
if (toasts.length >= TOAST_LIMIT) return;
|
|
162
|
+
toasts.push(time);
|
|
163
|
+
void deps.call("toast.show", [String(message), { ...options.tone ? { tone: options.tone } : {}, ...options.duration ? { duration: options.duration } : {} }]);
|
|
164
|
+
}
|
|
165
|
+
}),
|
|
166
|
+
ui: Object.freeze({
|
|
167
|
+
async confirm(options) {
|
|
168
|
+
return await callHost("ui.confirm", [options], "pos.ui.confirm") === true;
|
|
169
|
+
}
|
|
170
|
+
}),
|
|
171
|
+
navigate(to) {
|
|
172
|
+
void callHost("navigate", [to], "pos.navigate");
|
|
173
|
+
},
|
|
174
|
+
refresh() {
|
|
175
|
+
void callHost("refresh", [], "pos.refresh");
|
|
176
|
+
},
|
|
177
|
+
close() {
|
|
178
|
+
void callHost("close", [], "pos.close");
|
|
179
|
+
},
|
|
180
|
+
on(event, handler) {
|
|
181
|
+
if (event === "data-change") require2("data-change", "pos.on('data-change')");
|
|
182
|
+
else if (!deps.events?.includes(event)) throw new PosRuntimeError("unavailable", `event ${event} is not available in ${init.target}`);
|
|
183
|
+
let set = listeners.get(event);
|
|
184
|
+
if (!set) listeners.set(event, set = /* @__PURE__ */ new Set());
|
|
185
|
+
set.add(handler);
|
|
186
|
+
return () => set.delete(handler);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const emit = (event, payload) => {
|
|
190
|
+
for (const handler of [...listeners.get(event) ?? []]) handler(payload);
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
pos: Object.freeze(pos),
|
|
194
|
+
setData(next) {
|
|
195
|
+
data = next;
|
|
196
|
+
emit("data-change", next);
|
|
197
|
+
},
|
|
198
|
+
emit
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/page.ts
|
|
203
|
+
var PosApiError2 = PosApiError;
|
|
204
|
+
var PosRuntimeError2 = PosRuntimeError;
|
|
205
|
+
var currentLocation = () => {
|
|
206
|
+
const query = {};
|
|
207
|
+
new URLSearchParams(window.location.search).forEach((value, key) => {
|
|
208
|
+
query[key] = value;
|
|
209
|
+
});
|
|
210
|
+
return { path: window.location.pathname, query };
|
|
211
|
+
};
|
|
212
|
+
var toUrl = (data) => {
|
|
213
|
+
const search = new URLSearchParams(data.query).toString();
|
|
214
|
+
return `${data.path}${search ? `?${search}` : ""}`;
|
|
215
|
+
};
|
|
216
|
+
var connecting = null;
|
|
217
|
+
function connect(options = {}) {
|
|
218
|
+
connecting ?? (connecting = open(options.timeout ?? 1e4).catch((error) => {
|
|
219
|
+
connecting = null;
|
|
220
|
+
throw error;
|
|
221
|
+
}));
|
|
222
|
+
return connecting;
|
|
223
|
+
}
|
|
224
|
+
function open(timeout) {
|
|
225
|
+
if (typeof window === "undefined" || window.parent === window) {
|
|
226
|
+
return Promise.reject(new PosRuntimeError2("unavailable", "This page must be opened from Pancake POS"));
|
|
227
|
+
}
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
const timer = setTimeout(() => {
|
|
230
|
+
window.removeEventListener("message", onMessage);
|
|
231
|
+
reject(new PosRuntimeError2("unavailable", `POS did not respond within ${timeout} ms; open this page from Pancake POS`));
|
|
232
|
+
}, timeout);
|
|
233
|
+
const onMessage = (event) => {
|
|
234
|
+
if (event.source !== window.parent || event.data?.t !== "connect" || event.data.protocol !== PROTOCOL_VERSION) return;
|
|
235
|
+
const [port] = event.ports;
|
|
236
|
+
if (!port) return;
|
|
237
|
+
window.removeEventListener("message", onMessage);
|
|
238
|
+
port.onmessage = (message) => {
|
|
239
|
+
if (message.data?.t !== "page-init") return;
|
|
240
|
+
clearTimeout(timer);
|
|
241
|
+
resolve(start(port, message.data));
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
window.addEventListener("message", onMessage);
|
|
245
|
+
const ready = { t: "page-ready", protocol: PROTOCOL_VERSION };
|
|
246
|
+
window.parent.postMessage(ready, "*");
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
function start(port, init) {
|
|
250
|
+
const post = (message) => port.postMessage(message);
|
|
251
|
+
let callId = 0;
|
|
252
|
+
const pending = /* @__PURE__ */ new Map();
|
|
253
|
+
const call = (method, args) => new Promise((resolve, reject) => {
|
|
254
|
+
callId += 1;
|
|
255
|
+
pending.set(callId, { resolve, reject });
|
|
256
|
+
post({ t: "call", call: callId, method, args });
|
|
257
|
+
});
|
|
258
|
+
const runtime = createPos({
|
|
259
|
+
init: { ...init, kind: "ui", data: currentLocation() },
|
|
260
|
+
events: ["primary-action"],
|
|
261
|
+
call,
|
|
262
|
+
fetch: window.fetch.bind(window)
|
|
263
|
+
});
|
|
264
|
+
let reported = "";
|
|
265
|
+
const report = () => {
|
|
266
|
+
const location = currentLocation();
|
|
267
|
+
const url = toUrl(location);
|
|
268
|
+
if (url === reported) return;
|
|
269
|
+
reported = url;
|
|
270
|
+
runtime.setData(location);
|
|
271
|
+
post({ t: "location", ...location });
|
|
272
|
+
};
|
|
273
|
+
for (const method of ["pushState", "replaceState"]) {
|
|
274
|
+
const original = history[method].bind(history);
|
|
275
|
+
history[method] = (...args) => {
|
|
276
|
+
original(...args);
|
|
277
|
+
report();
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
window.addEventListener("popstate", report);
|
|
281
|
+
report();
|
|
282
|
+
port.onmessage = ({ data: message }) => {
|
|
283
|
+
switch (message?.t) {
|
|
284
|
+
case "reply": {
|
|
285
|
+
const entry = pending.get(message.call);
|
|
286
|
+
if (!entry) return;
|
|
287
|
+
pending.delete(message.call);
|
|
288
|
+
if (message.ok) entry.resolve(message.value);
|
|
289
|
+
else entry.reject(Object.assign(new PosRuntimeError2("unavailable", message.error.message), { code: message.error.code }));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
case "navigate": {
|
|
293
|
+
const target = toUrl(message);
|
|
294
|
+
if (target === toUrl(currentLocation())) return;
|
|
295
|
+
history.pushState(null, "", target);
|
|
296
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state: null }));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
case "primary-action":
|
|
300
|
+
runtime.emit("primary-action");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const reportError = (error) => {
|
|
305
|
+
const value = error;
|
|
306
|
+
post({
|
|
307
|
+
t: "error",
|
|
308
|
+
error: {
|
|
309
|
+
code: "uncaught",
|
|
310
|
+
message: String(value?.message ?? error).slice(0, 1024),
|
|
311
|
+
...typeof value?.stack === "string" ? { stack: value.stack.slice(0, 4096) } : {}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
window.addEventListener("error", (event) => reportError(event.error ?? event.message));
|
|
316
|
+
window.addEventListener("unhandledrejection", (event) => reportError(event.reason));
|
|
317
|
+
const page = Object.freeze({
|
|
318
|
+
setTitle(title) {
|
|
319
|
+
void call("page.setTitle", [String(title).slice(0, 200)]);
|
|
320
|
+
},
|
|
321
|
+
setPrimaryAction(action) {
|
|
322
|
+
void call("page.setPrimaryAction", [action ? { label: String(action.label).slice(0, 50), disabled: !!action.disabled, loading: !!action.loading } : null]);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
const pos = Object.defineProperties({}, { ...Object.getOwnPropertyDescriptors(runtime.pos), page: { value: page, enumerable: true } });
|
|
326
|
+
return Object.freeze(pos);
|
|
327
|
+
}
|
|
328
|
+
export {
|
|
329
|
+
PosApiError2 as PosApiError,
|
|
330
|
+
PosRuntimeError2 as PosRuntimeError,
|
|
331
|
+
connect
|
|
332
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
export type Resource = 'order' | 'product' | 'purchase' | 'export' | 'warehouse_transfer';
|
|
2
|
+
export type OrderRef = {
|
|
3
|
+
/** Mã đơn hiển thị (mã tuỳ chỉnh nếu shop dùng, ngược lại là mã hệ thống). */
|
|
4
|
+
id: string | number;
|
|
5
|
+
/** Mã đơn hệ thống, như trường `system_id` của đơn trong Open API: `/shops/{shop_id}/orders/{system_id}`. */
|
|
6
|
+
system_id: number;
|
|
7
|
+
status: number;
|
|
8
|
+
updated_at: string;
|
|
9
|
+
};
|
|
10
|
+
export type OrderDraft = {
|
|
11
|
+
status: number;
|
|
12
|
+
customer_id: string | null;
|
|
13
|
+
bill_full_name: string | null;
|
|
14
|
+
bill_phone_number: string | null;
|
|
15
|
+
warehouse_id: string | null;
|
|
16
|
+
tags: (number | string)[];
|
|
17
|
+
note: string | null;
|
|
18
|
+
items: {
|
|
19
|
+
variation_id: string;
|
|
20
|
+
product_id: string;
|
|
21
|
+
quantity: number;
|
|
22
|
+
price: number;
|
|
23
|
+
}[];
|
|
24
|
+
total_price: number;
|
|
25
|
+
};
|
|
26
|
+
export type ProductRef = {
|
|
27
|
+
id: string;
|
|
28
|
+
display_id: number;
|
|
29
|
+
custom_id: string | null;
|
|
30
|
+
name: string;
|
|
31
|
+
is_hide: boolean;
|
|
32
|
+
updated_at: string;
|
|
33
|
+
};
|
|
34
|
+
export type ProductDraft = {
|
|
35
|
+
name: string;
|
|
36
|
+
custom_id: string | null;
|
|
37
|
+
category_ids: number[];
|
|
38
|
+
brand_id: number | null;
|
|
39
|
+
tags: (number | string)[];
|
|
40
|
+
variations: {
|
|
41
|
+
id: string | null;
|
|
42
|
+
custom_id: string | null;
|
|
43
|
+
barcode: string | null;
|
|
44
|
+
retail_price: number;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
47
|
+
export type PurchaseRef = {
|
|
48
|
+
id: string;
|
|
49
|
+
display_id: number;
|
|
50
|
+
custom_id: string | null;
|
|
51
|
+
status: number;
|
|
52
|
+
warehouse_id: string;
|
|
53
|
+
updated_at: string;
|
|
54
|
+
};
|
|
55
|
+
export type PurchaseDraft = {
|
|
56
|
+
status: number;
|
|
57
|
+
warehouse_id: string | null;
|
|
58
|
+
supplier_products_id: string | null;
|
|
59
|
+
tags: (number | string)[];
|
|
60
|
+
note: string | null;
|
|
61
|
+
items: {
|
|
62
|
+
variation_id: string;
|
|
63
|
+
quantity: number;
|
|
64
|
+
imported_price: number;
|
|
65
|
+
}[];
|
|
66
|
+
total_price: number;
|
|
67
|
+
};
|
|
68
|
+
export type ExportRef = {
|
|
69
|
+
id: string;
|
|
70
|
+
display_id: number;
|
|
71
|
+
custom_id: string | null;
|
|
72
|
+
status: number;
|
|
73
|
+
type: number;
|
|
74
|
+
warehouse_id: string;
|
|
75
|
+
updated_at: string;
|
|
76
|
+
};
|
|
77
|
+
export type ExportDraft = {
|
|
78
|
+
status: number;
|
|
79
|
+
type: number;
|
|
80
|
+
warehouse_id: string | null;
|
|
81
|
+
tags: (number | string)[];
|
|
82
|
+
note: string | null;
|
|
83
|
+
export_items: {
|
|
84
|
+
variation_id: string;
|
|
85
|
+
quantity: number;
|
|
86
|
+
}[];
|
|
87
|
+
};
|
|
88
|
+
export type WarehouseTransferRef = {
|
|
89
|
+
id: string;
|
|
90
|
+
display_id: number;
|
|
91
|
+
custom_id: string | null;
|
|
92
|
+
status: number;
|
|
93
|
+
from_warehouse_id: string;
|
|
94
|
+
to_warehouse_id: string;
|
|
95
|
+
updated_status_at: string;
|
|
96
|
+
};
|
|
97
|
+
export type WarehouseTransferDraft = {
|
|
98
|
+
status: number;
|
|
99
|
+
from_warehouse_id: string | null;
|
|
100
|
+
to_warehouse_id: string | null;
|
|
101
|
+
note: string | null;
|
|
102
|
+
items: {
|
|
103
|
+
variation_id: string;
|
|
104
|
+
quantity: number;
|
|
105
|
+
}[];
|
|
106
|
+
};
|
|
107
|
+
interface Refs {
|
|
108
|
+
order: OrderRef;
|
|
109
|
+
product: ProductRef;
|
|
110
|
+
purchase: PurchaseRef;
|
|
111
|
+
export: ExportRef;
|
|
112
|
+
warehouse_transfer: WarehouseTransferRef;
|
|
113
|
+
}
|
|
114
|
+
interface Drafts {
|
|
115
|
+
order: OrderDraft;
|
|
116
|
+
product: ProductDraft;
|
|
117
|
+
purchase: PurchaseDraft;
|
|
118
|
+
export: ExportDraft;
|
|
119
|
+
warehouse_transfer: WarehouseTransferDraft;
|
|
120
|
+
}
|
|
121
|
+
export type RefOf<R extends Resource> = Refs[R];
|
|
122
|
+
export type DraftOf<R extends Resource> = Drafts[R];
|
|
123
|
+
/** Form chi tiết: `*.details.action`, `*.details.block`, `*.before_submit`. */
|
|
124
|
+
export type DetailsData<R extends Resource = Resource> = {
|
|
125
|
+
resource: R;
|
|
126
|
+
mode: 'create' | 'edit';
|
|
127
|
+
/** null khi bản ghi chưa lưu. */
|
|
128
|
+
record: RefOf<R> | null;
|
|
129
|
+
/** Dữ liệu form đang hiển thị, gồm cả phần chưa lưu. */
|
|
130
|
+
draft: DraftOf<R>;
|
|
131
|
+
};
|
|
132
|
+
/** Một dòng của danh sách: `*.index.action`. */
|
|
133
|
+
export type IndexData<R extends Resource = Resource> = {
|
|
134
|
+
resource: R;
|
|
135
|
+
record: RefOf<R>;
|
|
136
|
+
};
|
|
137
|
+
/** Thanh thao tác hàng loạt: `*.index.selection-action` (tối đa 500 bản ghi). */
|
|
138
|
+
export type SelectionData<R extends Resource = Resource> = {
|
|
139
|
+
resource: R;
|
|
140
|
+
records: RefOf<R>[];
|
|
141
|
+
};
|
|
142
|
+
export type AfterSaveInput<R extends Resource = Resource> = {
|
|
143
|
+
resource: R;
|
|
144
|
+
mode: 'create' | 'edit';
|
|
145
|
+
record: RefOf<R>;
|
|
146
|
+
};
|
|
147
|
+
export type BeforeSubmitInput<R extends Resource = Resource> = DetailsData<R>;
|
|
148
|
+
/** `message` tối đa 200 ký tự, hiện cho người dùng. */
|
|
149
|
+
export type BeforeSubmitResult = {
|
|
150
|
+
ok: true;
|
|
151
|
+
} | {
|
|
152
|
+
ok: false;
|
|
153
|
+
message: string;
|
|
154
|
+
};
|
|
155
|
+
export type HeadlessActionResult = {
|
|
156
|
+
message?: string;
|
|
157
|
+
refresh?: boolean;
|
|
158
|
+
} | void;
|
|
159
|
+
/** `app.page`: đường dẫn sau `/apps/<handle>` và query của URL POS. */
|
|
160
|
+
export type PageData = {
|
|
161
|
+
path: string;
|
|
162
|
+
query: Record<string, string>;
|
|
163
|
+
};
|
|
164
|
+
export type DetailsTarget<R extends Resource = Resource> = `${R}.details.action` | `${R}.details.block`;
|
|
165
|
+
export type Target = 'app.page' | DetailsTarget | `${Resource}.index.action` | `${Resource}.index.selection-action` | `${Resource}.before_submit` | `${Resource}.after_save`;
|
|
166
|
+
type ResourceOf<T extends string> = T extends `${infer R extends Resource}.${string}` ? R : never;
|
|
167
|
+
/** `pos.data` và tham số của module theo target. */
|
|
168
|
+
export type DataOf<T extends Target> = T extends 'app.page' ? PageData : T extends `${string}.index.selection-action` ? SelectionData<ResourceOf<T>> : T extends `${string}.index.action` ? IndexData<ResourceOf<T>> : T extends `${string}.after_save` ? AfterSaveInput<ResourceOf<T>> : DetailsData<ResourceOf<T>>;
|
|
169
|
+
/** Giá trị module trả về theo target (action có giao diện và block tự render, không trả gì). */
|
|
170
|
+
export type ResultOf<T extends Target> = T extends `${string}.before_submit` ? BeforeSubmitResult : T extends `${string}.after_save` | `${string}.details.block` | 'app.page' ? void : HeadlessActionResult;
|
|
171
|
+
export type ToastOptions = {
|
|
172
|
+
tone?: 'success' | 'error' | 'info' | 'warning';
|
|
173
|
+
duration?: number;
|
|
174
|
+
};
|
|
175
|
+
export type ConfirmOptions = {
|
|
176
|
+
title: string;
|
|
177
|
+
content?: string;
|
|
178
|
+
okText?: string;
|
|
179
|
+
cancelText?: string;
|
|
180
|
+
danger?: boolean;
|
|
181
|
+
};
|
|
182
|
+
export type NavigateTarget = {
|
|
183
|
+
page: string;
|
|
184
|
+
path?: string;
|
|
185
|
+
query?: Record<string, string>;
|
|
186
|
+
};
|
|
187
|
+
type RequestOptions = {
|
|
188
|
+
query?: Record<string, unknown>;
|
|
189
|
+
signal?: AbortSignal;
|
|
190
|
+
};
|
|
191
|
+
export interface PosApi {
|
|
192
|
+
/** Gốc Open API, ví dụ `https://pos.pages.fm/api/v1`. */
|
|
193
|
+
readonly baseUrl: string;
|
|
194
|
+
request<T = any>(options: {
|
|
195
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
196
|
+
path: string;
|
|
197
|
+
query?: Record<string, unknown>;
|
|
198
|
+
body?: unknown;
|
|
199
|
+
signal?: AbortSignal;
|
|
200
|
+
}): Promise<T>;
|
|
201
|
+
get<T = any>(path: string, options?: RequestOptions): Promise<T>;
|
|
202
|
+
post<T = any>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
203
|
+
put<T = any>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
204
|
+
delete<T = any>(path: string, options?: RequestOptions): Promise<T>;
|
|
205
|
+
}
|
|
206
|
+
export type PosEventName = 'data-change' | 'primary-action';
|
|
207
|
+
export interface Pos<D = any> {
|
|
208
|
+
readonly apiVersion: string;
|
|
209
|
+
readonly target: string;
|
|
210
|
+
readonly extensionId: string;
|
|
211
|
+
readonly app: {
|
|
212
|
+
readonly id: string;
|
|
213
|
+
readonly handle: string;
|
|
214
|
+
readonly version: string;
|
|
215
|
+
};
|
|
216
|
+
readonly shop: {
|
|
217
|
+
readonly id: number;
|
|
218
|
+
readonly name: string;
|
|
219
|
+
readonly currency: string;
|
|
220
|
+
readonly country: string;
|
|
221
|
+
readonly timezone: string;
|
|
222
|
+
};
|
|
223
|
+
readonly user: {
|
|
224
|
+
readonly id: string;
|
|
225
|
+
readonly name: string;
|
|
226
|
+
readonly locale: string;
|
|
227
|
+
};
|
|
228
|
+
/** Dữ liệu ngữ cảnh, dạng theo target (`DataOf<target>`). */
|
|
229
|
+
readonly data: D;
|
|
230
|
+
readonly api: PosApi;
|
|
231
|
+
readonly session: {
|
|
232
|
+
getToken(): Promise<{
|
|
233
|
+
token: string;
|
|
234
|
+
expiresAt: string;
|
|
235
|
+
}>;
|
|
236
|
+
};
|
|
237
|
+
readonly toast: {
|
|
238
|
+
show(message: string, options?: ToastOptions): void;
|
|
239
|
+
};
|
|
240
|
+
readonly ui: {
|
|
241
|
+
confirm(options: ConfirmOptions): Promise<boolean>;
|
|
242
|
+
};
|
|
243
|
+
navigate(to: NavigateTarget): void;
|
|
244
|
+
refresh(): void;
|
|
245
|
+
close(): void;
|
|
246
|
+
on(event: 'data-change', handler: (data: D) => void): () => void;
|
|
247
|
+
on(event: PosEventName, handler: (payload: any) => void): () => void;
|
|
248
|
+
}
|
|
249
|
+
/** `pos` của trang iframe: thêm `pos.page`. */
|
|
250
|
+
export interface PosPage extends Pos<PageData> {
|
|
251
|
+
readonly page: {
|
|
252
|
+
setTitle(title: string): void;
|
|
253
|
+
setPrimaryAction(action: {
|
|
254
|
+
label: string;
|
|
255
|
+
disabled?: boolean;
|
|
256
|
+
loading?: boolean;
|
|
257
|
+
} | null): void;
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/** Lỗi khi gọi `pos.api`; dùng `error.name === 'PosApiError'` hoặc kiểm `status`. */
|
|
261
|
+
export interface PosApiError extends Error {
|
|
262
|
+
readonly name: 'PosApiError';
|
|
263
|
+
readonly status: number;
|
|
264
|
+
readonly code?: number | string;
|
|
265
|
+
readonly body?: unknown;
|
|
266
|
+
}
|
|
267
|
+
/** Lỗi khi gọi API của `pos` không có ở target này, hoặc POS từ chối. */
|
|
268
|
+
export interface PosRuntimeError extends Error {
|
|
269
|
+
readonly name: 'PosRuntimeError';
|
|
270
|
+
readonly code: string;
|
|
271
|
+
}
|
|
272
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pancake-pos/ui-extensions",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2026.1.0",
|
|
4
|
+
"description": "Types for pos and pos-* elements, defineExtension, and connect() for iframe pages of Pancake POS apps",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://pos.pages.fm/developers/apps/en",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pancake-vn/pos-app-platform.git",
|
|
11
|
+
"directory": "packages/ui-extensions"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./page": {
|
|
25
|
+
"types": "./dist/page.d.ts",
|
|
26
|
+
"default": "./dist/page.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"preact": "^10.19.0 || ^11.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"preact": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|