@pancake-pos/ui-extensions 2026.1.3 → 2026.1.4

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
@@ -104,6 +104,16 @@ export type WarehouseTransferDraft = {
104
104
  quantity: number;
105
105
  }[];
106
106
  };
107
+ /** Khách hàng (`customer.details.block`). `id` dùng với Open API: `/shops/{shop_id}/customers/{id}`. */
108
+ export type CustomerRef = {
109
+ id: string;
110
+ name: string;
111
+ phone_numbers: string[];
112
+ tags: (number | string)[];
113
+ level_id: string | null;
114
+ assigned_user_id: string | null;
115
+ updated_at: string | null;
116
+ };
107
117
  interface Refs {
108
118
  order: OrderRef;
109
119
  product: ProductRef;
@@ -145,13 +155,51 @@ export type AfterSaveInput<R extends Resource = Resource> = {
145
155
  record: RefOf<R>;
146
156
  };
147
157
  export type BeforeSubmitInput<R extends Resource = Resource> = DetailsData<R>;
158
+ /**
159
+ * 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ự),
160
+ * `add_tags` thêm thẻ theo mã thẻ của shop (≤ 20). Resource không có trường đó thì bị bỏ qua.
161
+ */
162
+ export type BeforeSubmitChanges = {
163
+ note?: string;
164
+ add_tags?: (number | string)[];
165
+ };
148
166
  /** `message` tối đa 200 ký tự, hiện cho người dùng. */
149
167
  export type BeforeSubmitResult = {
150
168
  ok: true;
169
+ changes?: BeforeSubmitChanges;
151
170
  } | {
152
171
  ok: false;
153
172
  message: string;
154
173
  };
174
+ /** Khối trong hồ sơ khách hàng: `customer.details.block`. */
175
+ export type CustomerData = {
176
+ resource: 'customer';
177
+ record: CustomerRef;
178
+ };
179
+ /** 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ó. */
180
+ export type PrintResource = 'order' | 'purchase' | 'export' | 'warehouse_transfer';
181
+ /** Bản ghi được chọn để in: `<resource>.print-action` (1–500 bản ghi). */
182
+ export type PrintData<R extends PrintResource = PrintResource> = {
183
+ resource: R;
184
+ records: RefOf<R>[];
185
+ };
186
+ /** HTML của bản in (≤ 2 MB). POS lọc bỏ script, chỉ giữ ảnh hợp lệ rồi in. */
187
+ export type PrintResult = {
188
+ html: string;
189
+ title?: string;
190
+ };
191
+ /** Bản ghi đang hiển thị trên trang danh sách: `<resource>.index.column` (tối đa 500). */
192
+ export type ColumnData<R extends Resource = Resource> = {
193
+ resource: R;
194
+ records: RefOf<R>[];
195
+ };
196
+ /**
197
+ * 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
198
+ * có trong `values` để trống.
199
+ */
200
+ export type ColumnResult = {
201
+ values: Record<string, string | number | null>;
202
+ };
155
203
  export type HeadlessActionResult = {
156
204
  message?: string;
157
205
  refresh?: boolean;
@@ -162,12 +210,12 @@ export type PageData = {
162
210
  query: Record<string, string>;
163
211
  };
164
212
  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`;
213
+ export type Target = 'app.page' | DetailsTarget | `${Resource}.index.action` | `${Resource}.index.selection-action` | `${Resource}.before_submit` | `${Resource}.after_save` | `${Resource}.index.column` | `${PrintResource}.print-action` | 'customer.details.block';
166
214
  type ResourceOf<T extends string> = T extends `${infer R extends Resource}.${string}` ? R : never;
167
215
  /** `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>>;
216
+ export type DataOf<T extends Target> = T extends 'app.page' ? PageData : T extends 'customer.details.block' ? 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<ResourceOf<T>> : DetailsData<ResourceOf<T>>;
169
217
  /** 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;
218
+ 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
219
  export type ToastOptions = {
172
220
  tone?: 'success' | 'error' | 'info' | 'warning';
173
221
  duration?: number;
@@ -204,6 +252,15 @@ export interface PosApi {
204
252
  delete<T = any>(path: string, options?: RequestOptions): Promise<T>;
205
253
  }
206
254
  export type PosEventName = 'data-change' | 'primary-action';
255
+ /** Thanh tiêu đề của POS trên trang `app.page` (iframe và Worker UI). */
256
+ export interface PosPageApi {
257
+ setTitle(title: string): void;
258
+ setPrimaryAction(action: {
259
+ label: string;
260
+ disabled?: boolean;
261
+ loading?: boolean;
262
+ } | null): void;
263
+ }
207
264
  export interface Pos<D = any> {
208
265
  readonly apiVersion: string;
209
266
  readonly target: string;
@@ -245,17 +302,12 @@ export interface Pos<D = any> {
245
302
  close(): void;
246
303
  on(event: 'data-change', handler: (data: D) => void): () => void;
247
304
  on(event: PosEventName, handler: (payload: any) => void): () => void;
305
+ /** Chỉ có ở `app.page` (iframe và Worker UI). */
306
+ readonly page?: PosPageApi;
248
307
  }
249
- /** `pos` của trang iframe: thêm `pos.page`. */
308
+ /** `pos` của trang (`connect()` ở trang iframe, biến `pos` của trang Worker UI): luôn có `pos.page`. */
250
309
  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
- };
310
+ readonly page: PosPageApi;
259
311
  }
260
312
  /** Lỗi khi gọi `pos.api`; dùng `error.name === 'PosApiError'` hoặc kiểm `status`. */
261
313
  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.4",
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",