@pancake-pos/ui-extensions 2026.1.3 → 2026.1.5

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.
@@ -59,6 +59,29 @@ export interface PosButtonElement extends PosButtonProps {
59
59
  focus(): void;
60
60
  scrollIntoView(): void;
61
61
  }
62
+ /** Thuộc tính của `<pos-chart>`. */
63
+ export interface PosChartProps {
64
+ type?: "line" | "bar" | "area" | "pie";
65
+ data?: (Record<string, string | number | null>)[];
66
+ xKey?: string;
67
+ series?: ({
68
+ key: string;
69
+ label: string;
70
+ format?: "number" | "money";
71
+ })[];
72
+ stacked?: boolean;
73
+ height?: number;
74
+ loading?: boolean;
75
+ emptyText?: string;
76
+ }
77
+ /** Sự kiện của `<pos-chart>` → kiểu `event.detail`. */
78
+ export interface PosChartEvents {
79
+ }
80
+ export interface PosChartAttributes extends PosChartProps, PosBaseAttributes {
81
+ }
82
+ export interface PosChartElement extends PosChartProps {
83
+ scrollIntoView(): void;
84
+ }
62
85
  /** Thuộc tính của `<pos-checkbox>`. */
63
86
  export interface PosCheckboxProps {
64
87
  label?: string;
@@ -116,6 +139,17 @@ export interface PosDividerAttributes extends PosDividerProps, PosBaseAttributes
116
139
  export interface PosDividerElement extends PosDividerProps {
117
140
  scrollIntoView(): void;
118
141
  }
142
+ /** Thuộc tính của `<pos-filter-bar>`. */
143
+ export interface PosFilterBarProps {
144
+ }
145
+ /** Sự kiện của `<pos-filter-bar>` → kiểu `event.detail`. */
146
+ export interface PosFilterBarEvents {
147
+ }
148
+ export interface PosFilterBarAttributes extends PosFilterBarProps, PosBaseAttributes {
149
+ }
150
+ export interface PosFilterBarElement extends PosFilterBarProps {
151
+ scrollIntoView(): void;
152
+ }
119
153
  /** Thuộc tính của `<pos-grid>`. */
120
154
  export interface PosGridProps {
121
155
  columns?: number;
@@ -227,6 +261,20 @@ export interface PosNumberFieldElement extends PosNumberFieldProps {
227
261
  focus(): void;
228
262
  scrollIntoView(): void;
229
263
  }
264
+ /** Thuộc tính của `<pos-page>`. */
265
+ export interface PosPageProps {
266
+ heading?: string;
267
+ description?: string;
268
+ width?: "narrow" | "default" | "full";
269
+ }
270
+ /** Sự kiện của `<pos-page>` → kiểu `event.detail`. */
271
+ export interface PosPageEvents {
272
+ }
273
+ export interface PosPageAttributes extends PosPageProps, PosBaseAttributes {
274
+ }
275
+ export interface PosPageElement extends PosPageProps {
276
+ scrollIntoView(): void;
277
+ }
230
278
  /** Thuộc tính của `<pos-section>`. */
231
279
  export interface PosSectionProps {
232
280
  heading?: string;
@@ -332,6 +380,26 @@ export interface PosTableAttributes extends PosTableProps, PosBaseAttributes {
332
380
  export interface PosTableElement extends PosTableProps {
333
381
  scrollIntoView(): void;
334
382
  }
383
+ /** Thuộc tính của `<pos-tabs>`. */
384
+ export interface PosTabsProps {
385
+ tabs?: {
386
+ id: string;
387
+ label: string;
388
+ badge?: number;
389
+ disabled?: boolean;
390
+ }[];
391
+ selected?: string;
392
+ }
393
+ /** Sự kiện của `<pos-tabs>` → kiểu `event.detail`. */
394
+ export interface PosTabsEvents {
395
+ change: string;
396
+ }
397
+ export interface PosTabsAttributes extends PosTabsProps, PosBaseAttributes {
398
+ onChange?: (event: PosEvent<PosTabsEvents["change"]>) => void;
399
+ }
400
+ export interface PosTabsElement extends PosTabsProps {
401
+ scrollIntoView(): void;
402
+ }
335
403
  /** Thuộc tính của `<pos-text>`. */
336
404
  export interface PosTextProps {
337
405
  tone?: "default" | "subdued" | "success" | "warning" | "critical";
@@ -376,9 +444,11 @@ export interface PosIntrinsicElements {
376
444
  "pos-badge": PosBadgeAttributes;
377
445
  "pos-banner": PosBannerAttributes;
378
446
  "pos-button": PosButtonAttributes;
447
+ "pos-chart": PosChartAttributes;
379
448
  "pos-checkbox": PosCheckboxAttributes;
380
449
  "pos-date-picker": PosDatePickerAttributes;
381
450
  "pos-divider": PosDividerAttributes;
451
+ "pos-filter-bar": PosFilterBarAttributes;
382
452
  "pos-grid": PosGridAttributes;
383
453
  "pos-heading": PosHeadingAttributes;
384
454
  "pos-image": PosImageAttributes;
@@ -386,11 +456,13 @@ export interface PosIntrinsicElements {
386
456
  "pos-modal": PosModalAttributes;
387
457
  "pos-money": PosMoneyAttributes;
388
458
  "pos-number-field": PosNumberFieldAttributes;
459
+ "pos-page": PosPageAttributes;
389
460
  "pos-section": PosSectionAttributes;
390
461
  "pos-select": PosSelectAttributes;
391
462
  "pos-spinner": PosSpinnerAttributes;
392
463
  "pos-stack": PosStackAttributes;
393
464
  "pos-table": PosTableAttributes;
465
+ "pos-tabs": PosTabsAttributes;
394
466
  "pos-text": PosTextAttributes;
395
467
  "pos-text-field": PosTextFieldAttributes;
396
468
  }
@@ -399,9 +471,11 @@ export interface PosHTMLElementTagNameMap {
399
471
  "pos-badge": Omit<HTMLElement, keyof PosBadgeElement> & PosBadgeElement;
400
472
  "pos-banner": Omit<HTMLElement, keyof PosBannerElement> & PosBannerElement;
401
473
  "pos-button": Omit<HTMLElement, keyof PosButtonElement> & PosButtonElement;
474
+ "pos-chart": Omit<HTMLElement, keyof PosChartElement> & PosChartElement;
402
475
  "pos-checkbox": Omit<HTMLElement, keyof PosCheckboxElement> & PosCheckboxElement;
403
476
  "pos-date-picker": Omit<HTMLElement, keyof PosDatePickerElement> & PosDatePickerElement;
404
477
  "pos-divider": Omit<HTMLElement, keyof PosDividerElement> & PosDividerElement;
478
+ "pos-filter-bar": Omit<HTMLElement, keyof PosFilterBarElement> & PosFilterBarElement;
405
479
  "pos-grid": Omit<HTMLElement, keyof PosGridElement> & PosGridElement;
406
480
  "pos-heading": Omit<HTMLElement, keyof PosHeadingElement> & PosHeadingElement;
407
481
  "pos-image": Omit<HTMLElement, keyof PosImageElement> & PosImageElement;
@@ -409,11 +483,13 @@ export interface PosHTMLElementTagNameMap {
409
483
  "pos-modal": Omit<HTMLElement, keyof PosModalElement> & PosModalElement;
410
484
  "pos-money": Omit<HTMLElement, keyof PosMoneyElement> & PosMoneyElement;
411
485
  "pos-number-field": Omit<HTMLElement, keyof PosNumberFieldElement> & PosNumberFieldElement;
486
+ "pos-page": Omit<HTMLElement, keyof PosPageElement> & PosPageElement;
412
487
  "pos-section": Omit<HTMLElement, keyof PosSectionElement> & PosSectionElement;
413
488
  "pos-select": Omit<HTMLElement, keyof PosSelectElement> & PosSelectElement;
414
489
  "pos-spinner": Omit<HTMLElement, keyof PosSpinnerElement> & PosSpinnerElement;
415
490
  "pos-stack": Omit<HTMLElement, keyof PosStackElement> & PosStackElement;
416
491
  "pos-table": Omit<HTMLElement, keyof PosTableElement> & PosTableElement;
492
+ "pos-tabs": Omit<HTMLElement, keyof PosTabsElement> & PosTabsElement;
417
493
  "pos-text": Omit<HTMLElement, keyof PosTextElement> & PosTextElement;
418
494
  "pos-text-field": Omit<HTMLElement, keyof PosTextFieldElement> & PosTextFieldElement;
419
495
  }
package/dist/page.js CHANGED
@@ -1,5 +1,8 @@
1
1
  // ../app-protocol/src/protocol.ts
2
2
  var PROTOCOL_VERSION = 1;
3
+ function hostEvents(context) {
4
+ return context === "page" ? ["primary-action"] : [];
5
+ }
3
6
  function extensionContext(kind, target) {
4
7
  if (kind === "hook") return "hook";
5
8
  if (kind === "headless") return "action-headless";
@@ -45,6 +48,9 @@ var PosApiError = class extends Error {
45
48
  this.name = "PosApiError";
46
49
  }
47
50
  };
51
+ function withData(value) {
52
+ return Object.defineProperties({}, Object.getOwnPropertyDescriptors(value));
53
+ }
48
54
  function buildQuery(query) {
49
55
  if (!query) return "";
50
56
  const params = new URLSearchParams();
@@ -60,6 +66,7 @@ function createPos(deps) {
60
66
  const { init } = deps;
61
67
  const now = deps.now ?? Date.now;
62
68
  const context = extensionContext(init.kind, init.target);
69
+ const events = hostEvents(context);
63
70
  const listeners = /* @__PURE__ */ new Map();
64
71
  let data = init.data;
65
72
  let token = { ...init.token };
@@ -179,18 +186,28 @@ function createPos(deps) {
179
186
  },
180
187
  on(event, handler) {
181
188
  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}`);
189
+ else if (!events.includes(event)) throw new PosRuntimeError("unavailable", `event ${event} is not available in ${init.target}`);
183
190
  let set = listeners.get(event);
184
191
  if (!set) listeners.set(event, set = /* @__PURE__ */ new Set());
185
192
  set.add(handler);
186
193
  return () => set.delete(handler);
187
194
  }
188
195
  };
196
+ const page = Object.freeze({
197
+ setTitle(title) {
198
+ void callHost("page.setTitle", [String(title).slice(0, 200)], "pos.page.setTitle");
199
+ },
200
+ setPrimaryAction(action) {
201
+ const value = action ? { label: String(action.label).slice(0, 50), disabled: !!action.disabled, loading: !!action.loading } : null;
202
+ void callHost("page.setPrimaryAction", [value], "pos.page.setPrimaryAction");
203
+ }
204
+ });
189
205
  const emit = (event, payload) => {
206
+ if (event !== "data-change" && !events.includes(event)) return;
190
207
  for (const handler of [...listeners.get(event) ?? []]) handler(payload);
191
208
  };
192
209
  return {
193
- pos: Object.freeze(pos),
210
+ pos: Object.freeze(context === "page" ? Object.defineProperty(withData(pos), "page", { value: page, enumerable: true }) : pos),
194
211
  setData(next) {
195
212
  data = next;
196
213
  emit("data-change", next);
@@ -257,7 +274,6 @@ function start(port, init) {
257
274
  });
258
275
  const runtime = createPos({
259
276
  init: { ...init, kind: "ui", data: currentLocation() },
260
- events: ["primary-action"],
261
277
  call,
262
278
  fetch: window.fetch.bind(window)
263
279
  });
@@ -314,16 +330,8 @@ function start(port, init) {
314
330
  };
315
331
  window.addEventListener("error", (event) => reportError(event.error ?? event.message));
316
332
  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);
333
+ const pos = runtime.pos;
334
+ return pos;
327
335
  }
328
336
  export {
329
337
  PosApiError2 as PosApiError,
package/dist/types.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export type Resource = 'order' | 'product' | 'purchase' | 'export' | 'warehouse_transfer';
2
+ /** Resource có danh sách và action: 5 resource có form chi tiết và khách hàng (`customer.*`). */
3
+ export type ListResource = Resource | 'customer';
2
4
  export type OrderRef = {
3
5
  /** Mã đơn hiển thị (mã tuỳ chỉnh nếu shop dùng, ngược lại là mã hệ thống). */
4
6
  id: string | number;
@@ -104,12 +106,23 @@ export type WarehouseTransferDraft = {
104
106
  quantity: number;
105
107
  }[];
106
108
  };
109
+ /** Khách hàng (`customer.*`). `id` dùng với Open API: `/shops/{shop_id}/customers/{id}`. */
110
+ export type CustomerRef = {
111
+ id: string;
112
+ name: string;
113
+ phone_numbers: string[];
114
+ tags: (number | string)[];
115
+ level_id: string | null;
116
+ assigned_user_id: string | null;
117
+ updated_at: string | null;
118
+ };
107
119
  interface Refs {
108
120
  order: OrderRef;
109
121
  product: ProductRef;
110
122
  purchase: PurchaseRef;
111
123
  export: ExportRef;
112
124
  warehouse_transfer: WarehouseTransferRef;
125
+ customer: CustomerRef;
113
126
  }
114
127
  interface Drafts {
115
128
  order: OrderDraft;
@@ -118,7 +131,7 @@ interface Drafts {
118
131
  export: ExportDraft;
119
132
  warehouse_transfer: WarehouseTransferDraft;
120
133
  }
121
- export type RefOf<R extends Resource> = Refs[R];
134
+ export type RefOf<R extends ListResource> = Refs[R];
122
135
  export type DraftOf<R extends Resource> = Drafts[R];
123
136
  /** Form chi tiết: `*.details.action`, `*.details.block`, `*.before_submit`. */
124
137
  export type DetailsData<R extends Resource = Resource> = {
@@ -130,12 +143,12 @@ export type DetailsData<R extends Resource = Resource> = {
130
143
  draft: DraftOf<R>;
131
144
  };
132
145
  /** Một dòng của danh sách: `*.index.action`. */
133
- export type IndexData<R extends Resource = Resource> = {
146
+ export type IndexData<R extends ListResource = ListResource> = {
134
147
  resource: R;
135
148
  record: RefOf<R>;
136
149
  };
137
150
  /** 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> = {
151
+ export type SelectionData<R extends ListResource = ListResource> = {
139
152
  resource: R;
140
153
  records: RefOf<R>[];
141
154
  };
@@ -145,13 +158,51 @@ export type AfterSaveInput<R extends Resource = Resource> = {
145
158
  record: RefOf<R>;
146
159
  };
147
160
  export type BeforeSubmitInput<R extends Resource = Resource> = DetailsData<R>;
161
+ /**
162
+ * Thay đổi app muốn áp vào bản ghi trước khi lưu (`before_submit`): `note` ghi đè ghi chú (≤ 2000 ký tự),
163
+ * `add_tags` thêm thẻ theo mã thẻ của shop (≤ 20). Resource không có trường đó thì bị bỏ qua.
164
+ */
165
+ export type BeforeSubmitChanges = {
166
+ note?: string;
167
+ add_tags?: (number | string)[];
168
+ };
148
169
  /** `message` tối đa 200 ký tự, hiện cho người dùng. */
149
170
  export type BeforeSubmitResult = {
150
171
  ok: true;
172
+ changes?: BeforeSubmitChanges;
151
173
  } | {
152
174
  ok: false;
153
175
  message: string;
154
176
  };
177
+ /** Hồ sơ khách hàng: `customer.details.block`, `customer.details.action` (cùng dạng `IndexData<'customer'>`). */
178
+ export type CustomerData = {
179
+ resource: 'customer';
180
+ record: CustomerRef;
181
+ };
182
+ /** Resource có mẫu in của app (`<resource>.print-action`); sản phẩm chỉ in mã vạch nên không có. */
183
+ export type PrintResource = 'order' | 'purchase' | 'export' | 'warehouse_transfer';
184
+ /** Bản ghi được chọn để in: `<resource>.print-action` (1–500 bản ghi). */
185
+ export type PrintData<R extends PrintResource = PrintResource> = {
186
+ resource: R;
187
+ records: RefOf<R>[];
188
+ };
189
+ /** HTML của bản in (≤ 2 MB). POS lọc bỏ script, chỉ giữ ảnh hợp lệ rồi in. */
190
+ export type PrintResult = {
191
+ html: string;
192
+ title?: string;
193
+ };
194
+ /** Bản ghi đang hiển thị trên trang danh sách: `<resource>.index.column` (tối đa 500). */
195
+ export type ColumnData<R extends ListResource = ListResource> = {
196
+ resource: R;
197
+ records: RefOf<R>[];
198
+ };
199
+ /**
200
+ * Giá trị của ô theo mã bản ghi: `system_id` với đơn, `id` với resource khác (chuỗi tối đa 200 ký tự). Bản ghi không
201
+ * có trong `values` để trống.
202
+ */
203
+ export type ColumnResult = {
204
+ values: Record<string, string | number | null>;
205
+ };
155
206
  export type HeadlessActionResult = {
156
207
  message?: string;
157
208
  refresh?: boolean;
@@ -162,12 +213,13 @@ export type PageData = {
162
213
  query: Record<string, string>;
163
214
  };
164
215
  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;
216
+ export type Target = 'app.page' | DetailsTarget | `${ListResource}.index.action` | `${ListResource}.index.selection-action` | `${Resource}.before_submit` | `${Resource}.after_save` | `${ListResource}.index.column` | `${PrintResource}.print-action` | 'customer.details.block' | 'customer.details.action';
217
+ type ResourceOf<T extends string> = T extends `${infer R extends ListResource}.${string}` ? R : never;
218
+ type FormResourceOf<T extends string> = Extract<ResourceOf<T>, Resource>;
167
219
  /** `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>>;
220
+ export type DataOf<T extends Target> = T extends 'app.page' ? PageData : T extends 'customer.details.block' | 'customer.details.action' ? CustomerData : T extends `${infer R extends PrintResource}.print-action` ? PrintData<R> : T extends `${string}.index.column` ? ColumnData<ResourceOf<T>> : T extends `${string}.index.selection-action` ? SelectionData<ResourceOf<T>> : T extends `${string}.index.action` ? IndexData<ResourceOf<T>> : T extends `${string}.after_save` ? AfterSaveInput<FormResourceOf<T>> : DetailsData<FormResourceOf<T>>;
169
221
  /** 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;
222
+ export type ResultOf<T extends Target> = T extends `${string}.print-action` ? PrintResult : T extends `${string}.index.column` ? ColumnResult : T extends `${string}.before_submit` ? BeforeSubmitResult : T extends `${string}.after_save` | `${string}.details.block` | 'app.page' ? void : HeadlessActionResult;
171
223
  export type ToastOptions = {
172
224
  tone?: 'success' | 'error' | 'info' | 'warning';
173
225
  duration?: number;
@@ -204,6 +256,15 @@ export interface PosApi {
204
256
  delete<T = any>(path: string, options?: RequestOptions): Promise<T>;
205
257
  }
206
258
  export type PosEventName = 'data-change' | 'primary-action';
259
+ /** Thanh tiêu đề của POS trên trang `app.page` (iframe và Worker UI). */
260
+ export interface PosPageApi {
261
+ setTitle(title: string): void;
262
+ setPrimaryAction(action: {
263
+ label: string;
264
+ disabled?: boolean;
265
+ loading?: boolean;
266
+ } | null): void;
267
+ }
207
268
  export interface Pos<D = any> {
208
269
  readonly apiVersion: string;
209
270
  readonly target: string;
@@ -245,17 +306,12 @@ export interface Pos<D = any> {
245
306
  close(): void;
246
307
  on(event: 'data-change', handler: (data: D) => void): () => void;
247
308
  on(event: PosEventName, handler: (payload: any) => void): () => void;
309
+ /** Chỉ có ở `app.page` (iframe và Worker UI). */
310
+ readonly page?: PosPageApi;
248
311
  }
249
- /** `pos` của trang iframe: thêm `pos.page`. */
312
+ /** `pos` của trang (`connect()` ở trang iframe, biến `pos` của trang Worker UI): luôn có `pos.page`. */
250
313
  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
- };
314
+ readonly page: PosPageApi;
259
315
  }
260
316
  /** Lỗi khi gọi `pos.api`; dùng `error.name === 'PosApiError'` hoặc kiểm `status`. */
261
317
  export interface PosApiError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pancake-pos/ui-extensions",
3
- "version": "2026.1.3",
3
+ "version": "2026.1.5",
4
4
  "description": "Types for pos and pos-* elements, defineExtension, and connect() for iframe pages of Pancake POS apps",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",