@open-mercato/ui 0.6.7-develop.6758.1.697eade236 → 0.6.7-develop.6768.1.9d2c4efc43

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.
@@ -1,3 +1,7 @@
1
+ import {
2
+ crudFormExtensionSpotId,
3
+ dataTableExtensionSpotId
4
+ } from "@open-mercato/shared/modules/widgets/extension-points";
1
5
  const BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID = "backend:record:current";
2
6
  const BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID = "backend:layout:top";
3
7
  const BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID = "backend:layout:footer";
@@ -10,31 +14,32 @@ const BACKEND_SIDEBAR_NAV_FOOTER_INJECTION_SPOT_ID = "backend:sidebar:nav:footer
10
14
  const GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID = "global:sidebar:status-badges";
11
15
  const GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID = "global:header:status-indicators";
12
16
  const CrudFormInjectionSpots = {
13
- base: (entityId) => `crud-form:${entityId}`,
14
- beforeFields: (entityId) => `crud-form:${entityId}:before-fields`,
15
- afterFields: (entityId) => `crud-form:${entityId}:after-fields`,
16
- header: (entityId) => `crud-form:${entityId}:header`,
17
- footer: (entityId) => `crud-form:${entityId}:footer`,
18
- sidebar: (entityId) => `crud-form:${entityId}:sidebar`,
19
- group: (entityId, groupId) => `crud-form:${entityId}:group:${groupId}`,
20
- fieldBefore: (entityId, fieldId) => `crud-form:${entityId}:field:${fieldId}:before`,
21
- fieldAfter: (entityId, fieldId) => `crud-form:${entityId}:field:${fieldId}:after`
17
+ base: (entityId) => crudFormExtensionSpotId(entityId),
18
+ beforeFields: (entityId) => crudFormExtensionSpotId(entityId, "before-fields"),
19
+ afterFields: (entityId) => crudFormExtensionSpotId(entityId, "after-fields"),
20
+ header: (entityId) => crudFormExtensionSpotId(entityId, "header"),
21
+ fields: (entityId) => crudFormExtensionSpotId(entityId, "fields"),
22
+ footer: (entityId) => crudFormExtensionSpotId(entityId, "footer"),
23
+ sidebar: (entityId) => crudFormExtensionSpotId(entityId, "sidebar"),
24
+ group: (entityId, groupId) => crudFormExtensionSpotId(entityId, `group:${groupId}`),
25
+ fieldBefore: (entityId, fieldId) => crudFormExtensionSpotId(entityId, `field:${fieldId}:before`),
26
+ fieldAfter: (entityId, fieldId) => crudFormExtensionSpotId(entityId, `field:${fieldId}:after`)
22
27
  };
23
28
  const DataTableInjectionSpots = {
24
- header: (tableId) => `data-table:${tableId}:header`,
25
- footer: (tableId) => `data-table:${tableId}:footer`,
26
- toolbar: (tableId) => `data-table:${tableId}:toolbar`,
29
+ header: (tableId) => dataTableExtensionSpotId(tableId, "header"),
30
+ footer: (tableId) => dataTableExtensionSpotId(tableId, "footer"),
31
+ toolbar: (tableId) => dataTableExtensionSpotId(tableId, "toolbar"),
27
32
  // Slot rendered immediately after the search input on the same row as the
28
33
  // FilterBar — intended for compact, icon-sized triggers (AI assistants,
29
34
  // saved view shortcuts, etc.). Hosts pass the resolved spot ID through to
30
35
  // FilterBar's `searchTrailing` prop. Stays empty when the table has no
31
36
  // search input.
32
- searchTrailing: (tableId) => `data-table:${tableId}:search-trailing`,
33
- emptyState: (tableId) => `data-table:${tableId}:empty-state`,
34
- columns: (tableId) => `data-table:${tableId}:columns`,
35
- rowActions: (tableId) => `data-table:${tableId}:row-actions`,
36
- bulkActions: (tableId) => `data-table:${tableId}:bulk-actions`,
37
- filters: (tableId) => `data-table:${tableId}:filters`
37
+ searchTrailing: (tableId) => dataTableExtensionSpotId(tableId, "search-trailing"),
38
+ emptyState: (tableId) => dataTableExtensionSpotId(tableId, "empty-state"),
39
+ columns: (tableId) => dataTableExtensionSpotId(tableId, "columns"),
40
+ rowActions: (tableId) => dataTableExtensionSpotId(tableId, "row-actions"),
41
+ bulkActions: (tableId) => dataTableExtensionSpotId(tableId, "bulk-actions"),
42
+ filters: (tableId) => dataTableExtensionSpotId(tableId, "filters")
38
43
  };
39
44
  const PORTAL_SIDEBAR_MAIN_INJECTION_SPOT_ID = "menu:portal:sidebar:main";
40
45
  const PORTAL_SIDEBAR_ACCOUNT_INJECTION_SPOT_ID = "menu:portal:sidebar:account";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/spotIds.ts"],
4
- "sourcesContent": ["import type { InjectionSpotId } from '@open-mercato/shared/modules/widgets/injection'\n\nexport const BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID: InjectionSpotId = 'backend:record:current'\nexport const BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID: InjectionSpotId = 'backend:layout:top'\nexport const BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID: InjectionSpotId = 'backend:layout:footer'\nexport const BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:top'\nexport const BACKEND_SIDEBAR_FOOTER_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:footer'\n\n// Standardized backend chrome spot ids\nexport const BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID: InjectionSpotId = 'backend:topbar:profile-menu'\nexport const BACKEND_TOPBAR_ACTIONS_INJECTION_SPOT_ID: InjectionSpotId = 'backend:topbar:actions'\nexport const BACKEND_SIDEBAR_NAV_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:nav'\nexport const BACKEND_SIDEBAR_NAV_FOOTER_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:nav:footer'\n\n// Standardized global status spot ids\nexport const GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID: InjectionSpotId = 'global:sidebar:status-badges'\nexport const GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID: InjectionSpotId = 'global:header:status-indicators'\n\n// Standardized pattern helpers\nexport const CrudFormInjectionSpots = {\n base: (entityId: string): InjectionSpotId => `crud-form:${entityId}`,\n beforeFields: (entityId: string): InjectionSpotId => `crud-form:${entityId}:before-fields`,\n afterFields: (entityId: string): InjectionSpotId => `crud-form:${entityId}:after-fields`,\n header: (entityId: string): InjectionSpotId => `crud-form:${entityId}:header`,\n footer: (entityId: string): InjectionSpotId => `crud-form:${entityId}:footer`,\n sidebar: (entityId: string): InjectionSpotId => `crud-form:${entityId}:sidebar`,\n group: (entityId: string, groupId: string): InjectionSpotId => `crud-form:${entityId}:group:${groupId}`,\n fieldBefore: (entityId: string, fieldId: string): InjectionSpotId => `crud-form:${entityId}:field:${fieldId}:before`,\n fieldAfter: (entityId: string, fieldId: string): InjectionSpotId => `crud-form:${entityId}:field:${fieldId}:after`,\n} as const\n\nexport const DataTableInjectionSpots = {\n header: (tableId: string): InjectionSpotId => `data-table:${tableId}:header`,\n footer: (tableId: string): InjectionSpotId => `data-table:${tableId}:footer`,\n toolbar: (tableId: string): InjectionSpotId => `data-table:${tableId}:toolbar`,\n // Slot rendered immediately after the search input on the same row as the\n // FilterBar \u2014 intended for compact, icon-sized triggers (AI assistants,\n // saved view shortcuts, etc.). Hosts pass the resolved spot ID through to\n // FilterBar's `searchTrailing` prop. Stays empty when the table has no\n // search input.\n searchTrailing: (tableId: string): InjectionSpotId => `data-table:${tableId}:search-trailing`,\n emptyState: (tableId: string): InjectionSpotId => `data-table:${tableId}:empty-state`,\n columns: (tableId: string): InjectionSpotId => `data-table:${tableId}:columns`,\n rowActions: (tableId: string): InjectionSpotId => `data-table:${tableId}:row-actions`,\n bulkActions: (tableId: string): InjectionSpotId => `data-table:${tableId}:bulk-actions`,\n filters: (tableId: string): InjectionSpotId => `data-table:${tableId}:filters`,\n} as const\n\n// Portal chrome spot IDs (FROZEN once shipped)\nexport const PORTAL_SIDEBAR_MAIN_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:sidebar:main'\nexport const PORTAL_SIDEBAR_ACCOUNT_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:sidebar:account'\nexport const PORTAL_HEADER_ACTIONS_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:header:actions'\nexport const PORTAL_USER_DROPDOWN_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:user-dropdown'\n\n// Portal page injection spots (FROZEN once shipped)\nexport const PortalInjectionSpots = {\n dashboardSections: 'portal:dashboard:sections' as InjectionSpotId,\n dashboardProfile: 'portal:dashboard:profile' as InjectionSpotId,\n dashboardSidebar: 'portal:dashboard:sidebar' as InjectionSpotId,\n pageBefore: (pageId: string): InjectionSpotId => `portal:${pageId}:before` as InjectionSpotId,\n pageAfter: (pageId: string): InjectionSpotId => `portal:${pageId}:after` as InjectionSpotId,\n} as const\n\nexport const DetailInjectionSpots = {\n header: (entityId: string): InjectionSpotId => `detail:${entityId}:header`,\n tabs: (entityId: string): InjectionSpotId => `detail:${entityId}:tabs`,\n sidebar: (entityId: string): InjectionSpotId => `detail:${entityId}:sidebar`,\n footer: (entityId: string): InjectionSpotId => `detail:${entityId}:footer`,\n statusBadges: (entityId: string): InjectionSpotId => `detail:${entityId}:status-badges`,\n} as const\n"],
5
- "mappings": "AAEO,MAAM,2CAA4D;AAClE,MAAM,uCAAwD;AAC9D,MAAM,0CAA2D;AACjE,MAAM,wCAAyD;AAC/D,MAAM,2CAA4D;AAGlE,MAAM,gDAAiE;AACvE,MAAM,2CAA4D;AAClE,MAAM,wCAAyD;AAC/D,MAAM,+CAAgE;AAGtE,MAAM,iDAAkE;AACxE,MAAM,oDAAqE;AAG3E,MAAM,yBAAyB;AAAA,EACpC,MAAM,CAAC,aAAsC,aAAa,QAAQ;AAAA,EAClE,cAAc,CAAC,aAAsC,aAAa,QAAQ;AAAA,EAC1E,aAAa,CAAC,aAAsC,aAAa,QAAQ;AAAA,EACzE,QAAQ,CAAC,aAAsC,aAAa,QAAQ;AAAA,EACpE,QAAQ,CAAC,aAAsC,aAAa,QAAQ;AAAA,EACpE,SAAS,CAAC,aAAsC,aAAa,QAAQ;AAAA,EACrE,OAAO,CAAC,UAAkB,YAAqC,aAAa,QAAQ,UAAU,OAAO;AAAA,EACrG,aAAa,CAAC,UAAkB,YAAqC,aAAa,QAAQ,UAAU,OAAO;AAAA,EAC3G,YAAY,CAAC,UAAkB,YAAqC,aAAa,QAAQ,UAAU,OAAO;AAC5G;AAEO,MAAM,0BAA0B;AAAA,EACrC,QAAQ,CAAC,YAAqC,cAAc,OAAO;AAAA,EACnE,QAAQ,CAAC,YAAqC,cAAc,OAAO;AAAA,EACnE,SAAS,CAAC,YAAqC,cAAc,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpE,gBAAgB,CAAC,YAAqC,cAAc,OAAO;AAAA,EAC3E,YAAY,CAAC,YAAqC,cAAc,OAAO;AAAA,EACvE,SAAS,CAAC,YAAqC,cAAc,OAAO;AAAA,EACpE,YAAY,CAAC,YAAqC,cAAc,OAAO;AAAA,EACvE,aAAa,CAAC,YAAqC,cAAc,OAAO;AAAA,EACxE,SAAS,CAAC,YAAqC,cAAc,OAAO;AACtE;AAGO,MAAM,wCAAyD;AAC/D,MAAM,2CAA4D;AAClE,MAAM,0CAA2D;AACjE,MAAM,yCAA0D;AAGhE,MAAM,uBAAuB;AAAA,EAClC,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,YAAY,CAAC,WAAoC,UAAU,MAAM;AAAA,EACjE,WAAW,CAAC,WAAoC,UAAU,MAAM;AAClE;AAEO,MAAM,uBAAuB;AAAA,EAClC,QAAQ,CAAC,aAAsC,UAAU,QAAQ;AAAA,EACjE,MAAM,CAAC,aAAsC,UAAU,QAAQ;AAAA,EAC/D,SAAS,CAAC,aAAsC,UAAU,QAAQ;AAAA,EAClE,QAAQ,CAAC,aAAsC,UAAU,QAAQ;AAAA,EACjE,cAAc,CAAC,aAAsC,UAAU,QAAQ;AACzE;",
4
+ "sourcesContent": ["import type { InjectionSpotId } from '@open-mercato/shared/modules/widgets/injection'\nimport {\n crudFormExtensionSpotId,\n dataTableExtensionSpotId,\n} from '@open-mercato/shared/modules/widgets/extension-points'\n\nexport const BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID: InjectionSpotId = 'backend:record:current'\nexport const BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID: InjectionSpotId = 'backend:layout:top'\nexport const BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID: InjectionSpotId = 'backend:layout:footer'\nexport const BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:top'\nexport const BACKEND_SIDEBAR_FOOTER_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:footer'\n\n// Standardized backend chrome spot ids\nexport const BACKEND_TOPBAR_PROFILE_MENU_INJECTION_SPOT_ID: InjectionSpotId = 'backend:topbar:profile-menu'\nexport const BACKEND_TOPBAR_ACTIONS_INJECTION_SPOT_ID: InjectionSpotId = 'backend:topbar:actions'\nexport const BACKEND_SIDEBAR_NAV_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:nav'\nexport const BACKEND_SIDEBAR_NAV_FOOTER_INJECTION_SPOT_ID: InjectionSpotId = 'backend:sidebar:nav:footer'\n\n// Standardized global status spot ids\nexport const GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID: InjectionSpotId = 'global:sidebar:status-badges'\nexport const GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID: InjectionSpotId = 'global:header:status-indicators'\n\n// Standardized pattern helpers\nexport const CrudFormInjectionSpots = {\n base: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId),\n beforeFields: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'before-fields'),\n afterFields: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'after-fields'),\n header: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'header'),\n fields: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'fields'),\n footer: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'footer'),\n sidebar: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'sidebar'),\n group: (entityId: string, groupId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, `group:${groupId}`),\n fieldBefore: (entityId: string, fieldId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, `field:${fieldId}:before`),\n fieldAfter: (entityId: string, fieldId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, `field:${fieldId}:after`),\n} as const\n\nexport const DataTableInjectionSpots = {\n header: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'header'),\n footer: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'footer'),\n toolbar: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'toolbar'),\n // Slot rendered immediately after the search input on the same row as the\n // FilterBar \u2014 intended for compact, icon-sized triggers (AI assistants,\n // saved view shortcuts, etc.). Hosts pass the resolved spot ID through to\n // FilterBar's `searchTrailing` prop. Stays empty when the table has no\n // search input.\n searchTrailing: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'search-trailing'),\n emptyState: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'empty-state'),\n columns: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'columns'),\n rowActions: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'row-actions'),\n bulkActions: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'bulk-actions'),\n filters: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'filters'),\n} as const\n\n// Portal chrome spot IDs (FROZEN once shipped)\nexport const PORTAL_SIDEBAR_MAIN_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:sidebar:main'\nexport const PORTAL_SIDEBAR_ACCOUNT_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:sidebar:account'\nexport const PORTAL_HEADER_ACTIONS_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:header:actions'\nexport const PORTAL_USER_DROPDOWN_INJECTION_SPOT_ID: InjectionSpotId = 'menu:portal:user-dropdown'\n\n// Portal page injection spots (FROZEN once shipped)\nexport const PortalInjectionSpots = {\n dashboardSections: 'portal:dashboard:sections' as InjectionSpotId,\n dashboardProfile: 'portal:dashboard:profile' as InjectionSpotId,\n dashboardSidebar: 'portal:dashboard:sidebar' as InjectionSpotId,\n pageBefore: (pageId: string): InjectionSpotId => `portal:${pageId}:before` as InjectionSpotId,\n pageAfter: (pageId: string): InjectionSpotId => `portal:${pageId}:after` as InjectionSpotId,\n} as const\n\nexport const DetailInjectionSpots = {\n header: (entityId: string): InjectionSpotId => `detail:${entityId}:header`,\n tabs: (entityId: string): InjectionSpotId => `detail:${entityId}:tabs`,\n sidebar: (entityId: string): InjectionSpotId => `detail:${entityId}:sidebar`,\n footer: (entityId: string): InjectionSpotId => `detail:${entityId}:footer`,\n statusBadges: (entityId: string): InjectionSpotId => `detail:${entityId}:status-badges`,\n} as const\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEA,MAAM,2CAA4D;AAClE,MAAM,uCAAwD;AAC9D,MAAM,0CAA2D;AACjE,MAAM,wCAAyD;AAC/D,MAAM,2CAA4D;AAGlE,MAAM,gDAAiE;AACvE,MAAM,2CAA4D;AAClE,MAAM,wCAAyD;AAC/D,MAAM,+CAAgE;AAGtE,MAAM,iDAAkE;AACxE,MAAM,oDAAqE;AAG3E,MAAM,yBAAyB;AAAA,EACpC,MAAM,CAAC,aAAsC,wBAAwB,QAAQ;AAAA,EAC7E,cAAc,CAAC,aAAsC,wBAAwB,UAAU,eAAe;AAAA,EACtG,aAAa,CAAC,aAAsC,wBAAwB,UAAU,cAAc;AAAA,EACpG,QAAQ,CAAC,aAAsC,wBAAwB,UAAU,QAAQ;AAAA,EACzF,QAAQ,CAAC,aAAsC,wBAAwB,UAAU,QAAQ;AAAA,EACzF,QAAQ,CAAC,aAAsC,wBAAwB,UAAU,QAAQ;AAAA,EACzF,SAAS,CAAC,aAAsC,wBAAwB,UAAU,SAAS;AAAA,EAC3F,OAAO,CAAC,UAAkB,YAAqC,wBAAwB,UAAU,SAAS,OAAO,EAAE;AAAA,EACnH,aAAa,CAAC,UAAkB,YAAqC,wBAAwB,UAAU,SAAS,OAAO,SAAS;AAAA,EAChI,YAAY,CAAC,UAAkB,YAAqC,wBAAwB,UAAU,SAAS,OAAO,QAAQ;AAChI;AAEO,MAAM,0BAA0B;AAAA,EACrC,QAAQ,CAAC,YAAqC,yBAAyB,SAAS,QAAQ;AAAA,EACxF,QAAQ,CAAC,YAAqC,yBAAyB,SAAS,QAAQ;AAAA,EACxF,SAAS,CAAC,YAAqC,yBAAyB,SAAS,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM1F,gBAAgB,CAAC,YAAqC,yBAAyB,SAAS,iBAAiB;AAAA,EACzG,YAAY,CAAC,YAAqC,yBAAyB,SAAS,aAAa;AAAA,EACjG,SAAS,CAAC,YAAqC,yBAAyB,SAAS,SAAS;AAAA,EAC1F,YAAY,CAAC,YAAqC,yBAAyB,SAAS,aAAa;AAAA,EACjG,aAAa,CAAC,YAAqC,yBAAyB,SAAS,cAAc;AAAA,EACnG,SAAS,CAAC,YAAqC,yBAAyB,SAAS,SAAS;AAC5F;AAGO,MAAM,wCAAyD;AAC/D,MAAM,2CAA4D;AAClE,MAAM,0CAA2D;AACjE,MAAM,yCAA0D;AAGhE,MAAM,uBAAuB;AAAA,EAClC,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,YAAY,CAAC,WAAoC,UAAU,MAAM;AAAA,EACjE,WAAW,CAAC,WAAoC,UAAU,MAAM;AAClE;AAEO,MAAM,uBAAuB;AAAA,EAClC,QAAQ,CAAC,aAAsC,UAAU,QAAQ;AAAA,EACjE,MAAM,CAAC,aAAsC,UAAU,QAAQ;AAAA,EAC/D,SAAS,CAAC,aAAsC,UAAU,QAAQ;AAAA,EAClE,QAAQ,CAAC,aAAsC,UAAU,QAAQ;AAAA,EACjE,cAAc,CAAC,aAAsC,UAAU,QAAQ;AACzE;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.7-develop.6758.1.697eade236",
3
+ "version": "0.6.7-develop.6768.1.9d2c4efc43",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -155,13 +155,13 @@
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.6.7-develop.6758.1.697eade236",
158
+ "@open-mercato/shared": "0.6.7-develop.6768.1.9d2c4efc43",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
- "@open-mercato/shared": "0.6.7-develop.6758.1.697eade236",
164
+ "@open-mercato/shared": "0.6.7-develop.6768.1.9d2c4efc43",
165
165
  "@testing-library/dom": "^10.4.1",
166
166
  "@testing-library/jest-dom": "^6.9.1",
167
167
  "@testing-library/react": "^16.3.1",
@@ -107,6 +107,7 @@ import type { InjectionFieldDefinition, FieldContext } from '@open-mercato/share
107
107
  import { insertByInjectionPlacement } from '@open-mercato/shared/modules/widgets/injection-position'
108
108
  import { evaluateInjectedVisibility } from './injection/visibility-utils'
109
109
  import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
110
+ import { crudFormExtensionSpotId, extensionSpotChildId } from '@open-mercato/shared/modules/widgets/extension-points'
110
111
  import { RichEditor, type RichEditorLabels } from '../primitives/rich-editor'
111
112
  import MarkdownField from './inputs/MarkdownField'
112
113
 
@@ -816,7 +817,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
816
817
  if (injectionSpotId) return injectionSpotId
817
818
  if (resolvedEntityIds.length) {
818
819
  const normalized = resolvedEntityIds[0].replace(/[:]+/g, '.')
819
- return `crud-form:${normalized}`
820
+ return crudFormExtensionSpotId(normalized)
820
821
  }
821
822
  return undefined
822
823
  }, [injectionSpotId, resolvedEntityIds])
@@ -825,7 +826,9 @@ export function CrudForm<TValues extends Record<string, unknown>>({
825
826
  if (resolvedEntityIds.length) return ComponentReplacementHandles.crudForm(resolvedEntityIds[0].replace(/[:]+/g, '.'))
826
827
  return ComponentReplacementHandles.crudForm('unknown')
827
828
  }, [replacementHandle, resolvedEntityIds])
828
- const headerInjectionSpotId = resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:header` : undefined
829
+ const headerInjectionSpotId = resolvedInjectionSpotId
830
+ ? extensionSpotChildId(resolvedInjectionSpotId, 'header')
831
+ : undefined
829
832
 
830
833
  const recordId = React.useMemo(() => {
831
834
  const raw = values.id
@@ -1083,7 +1086,9 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1083
1086
  triggerOnLoad: true,
1084
1087
  })
1085
1088
  const { widgets: injectedFieldWidgets } = useInjectionDataWidgets(
1086
- resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:fields` : '__disabled__:fields'
1089
+ resolvedInjectionSpotId
1090
+ ? extensionSpotChildId(resolvedInjectionSpotId, 'fields')
1091
+ : '__disabled__:fields'
1087
1092
  )
1088
1093
 
1089
1094
  const { triggerEvent: triggerInjectionEvent } = useInjectionSpotEvents(resolvedInjectionSpotId ?? '', injectionWidgets)
@@ -59,6 +59,7 @@ import type {
59
59
  InjectionRowActionDefinition,
60
60
  } from '@open-mercato/shared/modules/widgets/injection'
61
61
  import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
62
+ import { dataTableExtensionSpotId, extensionSpotChildId } from '@open-mercato/shared/modules/widgets/extension-points'
62
63
  import { insertByInjectionPlacement } from '@open-mercato/shared/modules/widgets/injection-position'
63
64
  import { useVirtualizer } from '@tanstack/react-virtual'
64
65
  import type {
@@ -551,6 +552,43 @@ export function writePerspectiveSnapshot(tableId: string, snapshot: PerspectiveS
551
552
  writeVersionedPreference(key, PERSPECTIVE_SNAPSHOT_VERSION, snapshot)
552
553
  }
553
554
 
555
+ /**
556
+ * Purge every browser-local DataTable perspective snapshot and active-view cookie
557
+ * across all tables in this origin. Snapshots persist the unsaved/live table state
558
+ * (column widths, order, visibility) keyed only by `tableId`, so they are shared by
559
+ * every account that signs in on the same browser profile. Call this at the auth
560
+ * identity boundary (login) so one user's unsaved layout never carries over to a
561
+ * different user — including a different tenant — reusing the same browser tab (#4185).
562
+ */
563
+ export function clearAllPerspectiveState(): void {
564
+ if (typeof window !== 'undefined') {
565
+ try {
566
+ const storage = window.localStorage
567
+ const staleKeys: string[] = []
568
+ for (let index = 0; index < storage.length; index += 1) {
569
+ const key = storage.key(index)
570
+ if (key && key.startsWith(`${PERSPECTIVE_STORAGE_PREFIX}:`)) staleKeys.push(key)
571
+ }
572
+ for (const key of staleKeys) storage.removeItem(key)
573
+ } catch {
574
+ // private mode / quota errors are non-fatal
575
+ }
576
+ }
577
+ if (typeof document !== 'undefined') {
578
+ try {
579
+ const cookies = document.cookie ? document.cookie.split(';') : []
580
+ for (const cookie of cookies) {
581
+ const name = cookie.split('=')[0]?.trim()
582
+ if (name && name.startsWith(`${PERSPECTIVE_COOKIE_PREFIX}:`)) {
583
+ document.cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax`
584
+ }
585
+ }
586
+ } catch {
587
+ // ignore — cookie access can throw in sandboxed contexts
588
+ }
589
+ }
590
+ }
591
+
554
592
  export function sanitizePerspectiveSettings(source?: PerspectiveSettings | null): PerspectiveSettings | null {
555
593
  if (!source || typeof source !== 'object') return null
556
594
  const forbidden = new Set(['__proto__', 'prototype', 'constructor'])
@@ -1352,8 +1390,8 @@ export function DataTable<T>({
1352
1390
  }, [injectionSpotId, perspective?.tableId, extensionTableIdProp])
1353
1391
  const resolvedInjectionSpotId =
1354
1392
  injectionSpotId
1355
- ?? (perspective?.tableId ? `data-table:${perspective.tableId}` : null)
1356
- ?? (extensionTableIdProp ? `data-table:${extensionTableIdProp}` : null)
1393
+ ?? (perspective?.tableId ? dataTableExtensionSpotId(perspective.tableId) : null)
1394
+ ?? (extensionTableIdProp ? dataTableExtensionSpotId(extensionTableIdProp) : null)
1357
1395
  const resolvedReplacementHandle = replacementHandle ?? ComponentReplacementHandles.dataTable(extensionTableId ?? 'unknown')
1358
1396
  const baseInjectionContext = React.useMemo(
1359
1397
  () => {
@@ -1376,32 +1414,32 @@ export function DataTable<T>({
1376
1414
  [injectionContext, perspective?.tableId, extensionTableId, title]
1377
1415
  )
1378
1416
  const headerInjectionSpotId = React.useMemo(
1379
- () => (resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:header` : null),
1417
+ () => (resolvedInjectionSpotId ? extensionSpotChildId(resolvedInjectionSpotId, 'header') : null),
1380
1418
  [resolvedInjectionSpotId]
1381
1419
  )
1382
1420
  const toolbarInjectionSpotId = React.useMemo(
1383
- () => (resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:toolbar` : null),
1421
+ () => (resolvedInjectionSpotId ? extensionSpotChildId(resolvedInjectionSpotId, 'toolbar') : null),
1384
1422
  [resolvedInjectionSpotId]
1385
1423
  )
1386
1424
  const searchTrailingInjectionSpotId = React.useMemo(
1387
- () => (resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:search-trailing` : null),
1425
+ () => (resolvedInjectionSpotId ? extensionSpotChildId(resolvedInjectionSpotId, 'search-trailing') : null),
1388
1426
  [resolvedInjectionSpotId]
1389
1427
  )
1390
1428
  const footerInjectionSpotId = React.useMemo(
1391
- () => (resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:footer` : null),
1429
+ () => (resolvedInjectionSpotId ? extensionSpotChildId(resolvedInjectionSpotId, 'footer') : null),
1392
1430
  [resolvedInjectionSpotId]
1393
1431
  )
1394
1432
  const { widgets: columnWidgets } = useInjectionDataWidgets(
1395
- extensionTableId ? `data-table:${extensionTableId}:columns` : '__disabled__:columns',
1433
+ extensionTableId ? dataTableExtensionSpotId(extensionTableId, 'columns') : '__disabled__:columns',
1396
1434
  )
1397
1435
  const { widgets: rowActionWidgets } = useInjectionDataWidgets(
1398
- extensionTableId ? `data-table:${extensionTableId}:row-actions` : '__disabled__:row-actions',
1436
+ extensionTableId ? dataTableExtensionSpotId(extensionTableId, 'row-actions') : '__disabled__:row-actions',
1399
1437
  )
1400
1438
  const { widgets: bulkActionWidgets } = useInjectionDataWidgets(
1401
- extensionTableId ? `data-table:${extensionTableId}:bulk-actions` : '__disabled__:bulk-actions',
1439
+ extensionTableId ? dataTableExtensionSpotId(extensionTableId, 'bulk-actions') : '__disabled__:bulk-actions',
1402
1440
  )
1403
1441
  const { widgets: filterWidgets } = useInjectionDataWidgets(
1404
- extensionTableId ? `data-table:${extensionTableId}:filters` : '__disabled__:filters',
1442
+ extensionTableId ? dataTableExtensionSpotId(extensionTableId, 'filters') : '__disabled__:filters',
1405
1443
  )
1406
1444
  const injectedColumnDefs = React.useMemo<{ def: ColumnDef<T, unknown>; placement: InjectionColumnDefinition['placement'] }[]>(() => {
1407
1445
  const entries: InjectionColumnDefinition[] = []
@@ -1,7 +1,8 @@
1
1
  /** @jest-environment jsdom */
2
- import { readPerspectiveSnapshot, writePerspectiveSnapshot } from '../DataTable'
2
+ import { clearAllPerspectiveState, readPerspectiveSnapshot, writePerspectiveSnapshot } from '../DataTable'
3
3
 
4
4
  const PREFIX = 'om_table_perspective_snapshot'
5
+ const COOKIE_PREFIX = 'om_table_perspective'
5
6
 
6
7
  describe('DataTable perspective snapshot storage (versioned envelope)', () => {
7
8
  beforeEach(() => {
@@ -52,3 +53,63 @@ describe('DataTable perspective snapshot storage (versioned envelope)', () => {
52
53
  expect(localStorage.getItem(`${PREFIX}:tbl5`)).toBeNull()
53
54
  })
54
55
  })
56
+
57
+ describe('clearAllPerspectiveState (tenant-isolation at the auth identity boundary, #4185)', () => {
58
+ beforeEach(() => {
59
+ localStorage.clear()
60
+ for (const cookie of document.cookie ? document.cookie.split(';') : []) {
61
+ const name = cookie.split('=')[0]?.trim()
62
+ if (name) document.cookie = `${name}=; Path=/; Max-Age=0`
63
+ }
64
+ })
65
+
66
+ it('removes every perspective snapshot across all tables', () => {
67
+ writePerspectiveSnapshot('customers-companies', {
68
+ perspectiveId: null,
69
+ settings: { columnSizing: { name: 210, status: 60 } },
70
+ updatedAt: 1,
71
+ })
72
+ writePerspectiveSnapshot('customers-people', {
73
+ perspectiveId: null,
74
+ settings: { columnSizing: { name: 300 } },
75
+ updatedAt: 2,
76
+ })
77
+
78
+ clearAllPerspectiveState()
79
+
80
+ expect(localStorage.getItem(`${PREFIX}:customers-companies`)).toBeNull()
81
+ expect(localStorage.getItem(`${PREFIX}:customers-people`)).toBeNull()
82
+ expect(readPerspectiveSnapshot('customers-companies')).toBeNull()
83
+ expect(readPerspectiveSnapshot('customers-people')).toBeNull()
84
+ })
85
+
86
+ it('leaves unrelated localStorage keys untouched', () => {
87
+ writePerspectiveSnapshot('customers-companies', {
88
+ perspectiveId: null,
89
+ settings: { columnSizing: { name: 210 } },
90
+ updatedAt: 1,
91
+ })
92
+ localStorage.setItem('om_login_tenant', 'acme')
93
+ localStorage.setItem('om:auth:identity', '123')
94
+
95
+ clearAllPerspectiveState()
96
+
97
+ expect(localStorage.getItem(`${PREFIX}:customers-companies`)).toBeNull()
98
+ expect(localStorage.getItem('om_login_tenant')).toBe('acme')
99
+ expect(localStorage.getItem('om:auth:identity')).toBe('123')
100
+ })
101
+
102
+ it('expires the active-view perspective cookies', () => {
103
+ document.cookie = `${COOKIE_PREFIX}:customers-companies=view-1; Path=/`
104
+ expect(document.cookie).toContain(`${COOKIE_PREFIX}:customers-companies=view-1`)
105
+
106
+ clearAllPerspectiveState()
107
+
108
+ expect(document.cookie).not.toContain(`${COOKIE_PREFIX}:customers-companies`)
109
+ })
110
+
111
+ it('is a no-op when nothing is stored', () => {
112
+ expect(() => clearAllPerspectiveState()).not.toThrow()
113
+ expect(localStorage.length).toBe(0)
114
+ })
115
+ })
@@ -0,0 +1,38 @@
1
+ import {
2
+ CRUD_FORM_EXTENSION_SURFACES,
3
+ DATA_TABLE_EXTENSION_SURFACES,
4
+ } from '@open-mercato/shared/modules/widgets/extension-points'
5
+ import { CrudFormInjectionSpots, DataTableInjectionSpots } from '../spotIds'
6
+
7
+ describe('standard extension point spot IDs', () => {
8
+ it('keeps every bound DataTable suffix aligned with the runtime helper', () => {
9
+ const tableId = 'catalog.products.list'
10
+ const boundSuffixes = DATA_TABLE_EXTENSION_SURFACES
11
+ .filter((surface) => surface.bound && surface.suffix)
12
+ .map((surface) => surface.suffix)
13
+
14
+ expect(boundSuffixes).toEqual([
15
+ 'header',
16
+ 'footer',
17
+ 'toolbar',
18
+ 'search-trailing',
19
+ 'columns',
20
+ 'row-actions',
21
+ 'bulk-actions',
22
+ 'filters',
23
+ ])
24
+ expect(DataTableInjectionSpots.emptyState(tableId)).toBe('data-table:catalog.products.list:empty-state')
25
+ })
26
+
27
+ it('keeps bound CrudForm spots distinct from helper-only spots', () => {
28
+ const entityId = 'catalog.product'
29
+ const boundKeys = CRUD_FORM_EXTENSION_SURFACES
30
+ .filter((surface) => surface.bound)
31
+ .map((surface) => surface.key)
32
+
33
+ expect(boundKeys).toEqual(['base', 'header', 'fields', 'replacement'])
34
+ expect(CrudFormInjectionSpots.base(entityId)).toBe('crud-form:catalog.product')
35
+ expect(CrudFormInjectionSpots.header(entityId)).toBe('crud-form:catalog.product:header')
36
+ expect(CrudFormInjectionSpots.fields(entityId)).toBe('crud-form:catalog.product:fields')
37
+ })
38
+ })
@@ -1,4 +1,8 @@
1
1
  import type { InjectionSpotId } from '@open-mercato/shared/modules/widgets/injection'
2
+ import {
3
+ crudFormExtensionSpotId,
4
+ dataTableExtensionSpotId,
5
+ } from '@open-mercato/shared/modules/widgets/extension-points'
2
6
 
3
7
  export const BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID: InjectionSpotId = 'backend:record:current'
4
8
  export const BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID: InjectionSpotId = 'backend:layout:top'
@@ -18,32 +22,33 @@ export const GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID: InjectionSpotId
18
22
 
19
23
  // Standardized pattern helpers
20
24
  export const CrudFormInjectionSpots = {
21
- base: (entityId: string): InjectionSpotId => `crud-form:${entityId}`,
22
- beforeFields: (entityId: string): InjectionSpotId => `crud-form:${entityId}:before-fields`,
23
- afterFields: (entityId: string): InjectionSpotId => `crud-form:${entityId}:after-fields`,
24
- header: (entityId: string): InjectionSpotId => `crud-form:${entityId}:header`,
25
- footer: (entityId: string): InjectionSpotId => `crud-form:${entityId}:footer`,
26
- sidebar: (entityId: string): InjectionSpotId => `crud-form:${entityId}:sidebar`,
27
- group: (entityId: string, groupId: string): InjectionSpotId => `crud-form:${entityId}:group:${groupId}`,
28
- fieldBefore: (entityId: string, fieldId: string): InjectionSpotId => `crud-form:${entityId}:field:${fieldId}:before`,
29
- fieldAfter: (entityId: string, fieldId: string): InjectionSpotId => `crud-form:${entityId}:field:${fieldId}:after`,
25
+ base: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId),
26
+ beforeFields: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'before-fields'),
27
+ afterFields: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'after-fields'),
28
+ header: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'header'),
29
+ fields: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'fields'),
30
+ footer: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'footer'),
31
+ sidebar: (entityId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, 'sidebar'),
32
+ group: (entityId: string, groupId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, `group:${groupId}`),
33
+ fieldBefore: (entityId: string, fieldId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, `field:${fieldId}:before`),
34
+ fieldAfter: (entityId: string, fieldId: string): InjectionSpotId => crudFormExtensionSpotId(entityId, `field:${fieldId}:after`),
30
35
  } as const
31
36
 
32
37
  export const DataTableInjectionSpots = {
33
- header: (tableId: string): InjectionSpotId => `data-table:${tableId}:header`,
34
- footer: (tableId: string): InjectionSpotId => `data-table:${tableId}:footer`,
35
- toolbar: (tableId: string): InjectionSpotId => `data-table:${tableId}:toolbar`,
38
+ header: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'header'),
39
+ footer: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'footer'),
40
+ toolbar: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'toolbar'),
36
41
  // Slot rendered immediately after the search input on the same row as the
37
42
  // FilterBar — intended for compact, icon-sized triggers (AI assistants,
38
43
  // saved view shortcuts, etc.). Hosts pass the resolved spot ID through to
39
44
  // FilterBar's `searchTrailing` prop. Stays empty when the table has no
40
45
  // search input.
41
- searchTrailing: (tableId: string): InjectionSpotId => `data-table:${tableId}:search-trailing`,
42
- emptyState: (tableId: string): InjectionSpotId => `data-table:${tableId}:empty-state`,
43
- columns: (tableId: string): InjectionSpotId => `data-table:${tableId}:columns`,
44
- rowActions: (tableId: string): InjectionSpotId => `data-table:${tableId}:row-actions`,
45
- bulkActions: (tableId: string): InjectionSpotId => `data-table:${tableId}:bulk-actions`,
46
- filters: (tableId: string): InjectionSpotId => `data-table:${tableId}:filters`,
46
+ searchTrailing: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'search-trailing'),
47
+ emptyState: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'empty-state'),
48
+ columns: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'columns'),
49
+ rowActions: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'row-actions'),
50
+ bulkActions: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'bulk-actions'),
51
+ filters: (tableId: string): InjectionSpotId => dataTableExtensionSpotId(tableId, 'filters'),
47
52
  } as const
48
53
 
49
54
  // Portal chrome spot IDs (FROZEN once shipped)