Skip to content

Commit aac941b

Browse files
committed
Update snippet documentation and improve UI plugin for disabling categories
1 parent 5ffd7d0 commit aac941b

25 files changed

Lines changed: 835 additions & 213 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@embedpdf/plugin-ui': minor
3+
---
4+
5+
Added `data-hidden-items` attribute for efficient CSS dependency rules.
6+
7+
**Problem**: Visibility dependency rules (e.g., hiding overflow buttons when all menu items are hidden) required exponential CSS rules when using category-based logic, causing stylesheet bloat.
8+
9+
**Solution**:
10+
11+
- Added `hiddenItems` state that tracks which item IDs are hidden based on disabled categories
12+
- Dependency rules now use `data-epdf-hid` attribute to check item IDs directly
13+
- CSS rules are now O(n) per breakpoint instead of O(m^n)
14+
15+
**New APIs**:
16+
17+
- `getHiddenItems()` - returns array of hidden item IDs
18+
- `onCategoryChanged` event now includes `hiddenItems` in payload
19+
- `extractItemCategories(schema)` - extracts item→categories mapping
20+
- `computeHiddenItems(itemCategories, disabledCategories)` - computes hidden items
21+
22+
**Breaking Changes**: None - existing `disabledCategories` API unchanged
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
'@embedpdf/snippet': minor
3+
---
4+
5+
Added global `disabledCategories` config and hierarchical categories for fine-grained feature control.
6+
7+
**Global `disabledCategories` Configuration**
8+
9+
Added `disabledCategories` to the root `PDFViewerConfig` that applies to both UI and Commands plugins:
10+
11+
```typescript
12+
const config: PDFViewerConfig = {
13+
src: 'document.pdf',
14+
// Disable all annotation and redaction features globally
15+
disabledCategories: ['annotation', 'redaction'],
16+
};
17+
```
18+
19+
Plugin-specific settings can override the global setting:
20+
21+
```typescript
22+
const config: PDFViewerConfig = {
23+
disabledCategories: ['annotation'], // Global default
24+
ui: {
25+
disabledCategories: ['redaction'], // Overrides for UI only
26+
},
27+
commands: {
28+
disabledCategories: [], // Re-enables all for commands
29+
},
30+
};
31+
```
32+
33+
**Hierarchical Categories**
34+
35+
All commands and UI schema items now have hierarchical categories for granular control:
36+
37+
- `annotation` - all annotation features
38+
- `annotation-markup` - highlight, underline, strikeout, squiggly
39+
- `annotation-highlight`, `annotation-underline`, etc.
40+
- `annotation-shape` - rectangle, circle, line, arrow, polygon, polyline
41+
- `annotation-rectangle`, `annotation-circle`, etc.
42+
- `annotation-ink`, `annotation-text`, `annotation-stamp`
43+
- `redaction` - all redaction features
44+
- `redaction-text`, `redaction-area`, `redaction-apply`, `redaction-clear`
45+
- `zoom` - all zoom features
46+
- `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`
47+
- `zoom-level` - all zoom level presets
48+
- `document` - document operations
49+
- `document-open`, `document-close`, `document-print`, `document-export`, `document-fullscreen`
50+
- `panel` - sidebar panels
51+
- `panel-sidebar`, `panel-search`, `panel-comment`, `panel-annotation-style`
52+
- `page` - page settings
53+
- `spread`, `scroll`, `rotate`
54+
- `history` - undo/redo
55+
- `history-undo`, `history-redo`
56+
- `mode` - viewer modes
57+
- `mode-view`, `mode-annotate`, `mode-shapes`, `mode-redact`
58+
- `tools` - tool buttons
59+
- `pan`, `pointer`, `capture`
60+
61+
Example: Disable only print functionality while keeping export:
62+
63+
```typescript
64+
disabledCategories: ['document-print'];
65+
```

packages/plugin-ui/src/lib/actions.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const CLOSE_ALL_MENUS = 'UI/CLOSE_ALL_MENUS';
2323

2424
// Category actions
2525
export const SET_DISABLED_CATEGORIES = 'UI/SET_DISABLED_CATEGORIES';
26+
export const SET_HIDDEN_ITEMS = 'UI/SET_HIDDEN_ITEMS';
2627

2728
export interface InitUIStateAction extends Action {
2829
type: typeof INIT_UI_STATE;
@@ -102,6 +103,11 @@ export interface SetDisabledCategoriesAction extends Action {
102103
payload: { categories: string[] };
103104
}
104105

106+
export interface SetHiddenItemsAction extends Action {
107+
type: typeof SET_HIDDEN_ITEMS;
108+
payload: { hiddenItems: string[] };
109+
}
110+
105111
export type UIAction =
106112
| InitUIStateAction
107113
| CleanupUIStateAction
@@ -116,7 +122,8 @@ export type UIAction =
116122
| OpenMenuAction
117123
| CloseMenuAction
118124
| CloseAllMenusAction
119-
| SetDisabledCategoriesAction;
125+
| SetDisabledCategoriesAction
126+
| SetHiddenItemsAction;
120127

121128
// Action creators
122129
export const initUIState = (documentId: string, schema: UISchema): InitUIStateAction => ({
@@ -213,3 +220,8 @@ export const setDisabledCategories = (categories: string[]): SetDisabledCategori
213220
type: SET_DISABLED_CATEGORIES,
214221
payload: { categories },
215222
});
223+
224+
export const setHiddenItems = (hiddenItems: string[]): SetHiddenItemsAction => ({
225+
type: SET_HIDDEN_ITEMS,
226+
payload: { hiddenItems },
227+
});

packages/plugin-ui/src/lib/reducer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
CLOSE_MENU,
1616
CLOSE_ALL_MENUS,
1717
SET_DISABLED_CATEGORIES,
18+
SET_HIDDEN_ITEMS,
1819
} from './actions';
1920

2021
export const initialDocumentState: UIDocumentState = {
@@ -28,6 +29,7 @@ export const initialDocumentState: UIDocumentState = {
2829
export const initialState: UIState = {
2930
documents: {},
3031
disabledCategories: [],
32+
hiddenItems: [],
3133
};
3234

3335
export const uiReducer = (state = initialState, action: UIAction): UIState => {
@@ -332,6 +334,13 @@ export const uiReducer = (state = initialState, action: UIAction): UIState => {
332334
};
333335
}
334336

337+
case SET_HIDDEN_ITEMS: {
338+
return {
339+
...state,
340+
hiddenItems: action.payload.hiddenItems,
341+
};
342+
}
343+
335344
default:
336345
return state;
337346
}

packages/plugin-ui/src/lib/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export interface UIState {
2121

2222
/** Globally disabled categories */
2323
disabledCategories: string[];
24+
25+
/** Item IDs that are hidden (computed from disabled categories) */
26+
hiddenItems: string[];
2427
}
2528

2629
/**
@@ -239,6 +242,7 @@ export interface UICapability {
239242
setDisabledCategories(categories: string[]): void;
240243
getDisabledCategories(): string[];
241244
isCategoryDisabled(category: string): boolean;
245+
getHiddenItems(): string[];
242246

243247
// Global events
244248
onToolbarChanged: EventHook<{
@@ -255,5 +259,5 @@ export interface UICapability {
255259
}>;
256260
onModalChanged: EventHook<{ documentId: string; modalId: string | null; isOpen: boolean }>;
257261
onMenuChanged: EventHook<{ documentId: string; menuId: string; isOpen: boolean }>;
258-
onCategoryChanged: EventHook<{ disabledCategories: string[] }>;
262+
onCategoryChanged: EventHook<{ disabledCategories: string[]; hiddenItems: string[] }>;
259263
}

packages/plugin-ui/src/lib/ui-plugin.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,25 @@ import {
4141
closeMenu,
4242
closeAllMenus,
4343
setDisabledCategories,
44+
setHiddenItems,
4445
} from './actions';
4546
import { mergeUISchema } from './utils/schema-merger';
46-
import { generateUIStylesheet, StylesheetConfig } from './utils';
47+
import {
48+
generateUIStylesheet,
49+
extractItemCategories,
50+
computeHiddenItems,
51+
StylesheetConfig,
52+
} from './utils';
4753

4854
export class UIPlugin extends BasePlugin<UIPluginConfig, UICapability, UIState, UIAction> {
4955
static readonly id = 'ui' as const;
5056

5157
private schema: UISchema;
5258
private stylesheetConfig: StylesheetConfig;
5359

60+
// Item categories mapping for computing hidden items
61+
private itemCategories: Map<string, string[]>;
62+
5463
// Stylesheet caching with locale awareness
5564
private cachedStylesheet: string | null = null;
5665
private cachedLocale: string | null = null;
@@ -60,7 +69,10 @@ export class UIPlugin extends BasePlugin<UIPluginConfig, UICapability, UIState,
6069
private i18nCleanup: (() => void) | null = null;
6170

6271
// Events
63-
private readonly categoryChanged$ = createBehaviorEmitter<{ disabledCategories: string[] }>();
72+
private readonly categoryChanged$ = createBehaviorEmitter<{
73+
disabledCategories: string[];
74+
hiddenItems: string[];
75+
}>();
6476
private readonly stylesheetInvalidated$ = createEmitter<void>();
6577

6678
private readonly toolbarChanged$ = createScopedEmitter<
@@ -90,9 +102,15 @@ export class UIPlugin extends BasePlugin<UIPluginConfig, UICapability, UIState,
90102
this.schema = config.schema;
91103
this.stylesheetConfig = config.stylesheetConfig || {};
92104

105+
// Extract item categories for computing hidden items
106+
this.itemCategories = extractItemCategories(this.schema);
107+
93108
// Initialize disabled categories from config
94109
if (config.disabledCategories?.length) {
95110
this.dispatch(setDisabledCategories(config.disabledCategories));
111+
// Also compute and dispatch hidden items
112+
const hiddenItems = computeHiddenItems(this.itemCategories, config.disabledCategories);
113+
this.dispatch(setHiddenItems(hiddenItems));
96114
}
97115

98116
this.i18n = registry.getPlugin<I18nPlugin>('i18n')?.provides() ?? null;
@@ -206,17 +224,23 @@ export class UIPlugin extends BasePlugin<UIPluginConfig, UICapability, UIState,
206224
const current = new Set(this.state.disabledCategories);
207225
if (!current.has(category)) {
208226
current.add(category);
209-
this.dispatch(setDisabledCategories(Array.from(current)));
210-
this.categoryChanged$.emit({ disabledCategories: Array.from(current) });
227+
const categories = Array.from(current);
228+
this.dispatch(setDisabledCategories(categories));
229+
const hiddenItems = computeHiddenItems(this.itemCategories, categories);
230+
this.dispatch(setHiddenItems(hiddenItems));
231+
this.categoryChanged$.emit({ disabledCategories: categories, hiddenItems });
211232
}
212233
}
213234

214235
private enableCategoryImpl(category: string): void {
215236
const current = new Set(this.state.disabledCategories);
216237
if (current.has(category)) {
217238
current.delete(category);
218-
this.dispatch(setDisabledCategories(Array.from(current)));
219-
this.categoryChanged$.emit({ disabledCategories: Array.from(current) });
239+
const categories = Array.from(current);
240+
this.dispatch(setDisabledCategories(categories));
241+
const hiddenItems = computeHiddenItems(this.itemCategories, categories);
242+
this.dispatch(setHiddenItems(hiddenItems));
243+
this.categoryChanged$.emit({ disabledCategories: categories, hiddenItems });
220244
}
221245
}
222246

@@ -230,7 +254,10 @@ export class UIPlugin extends BasePlugin<UIPluginConfig, UICapability, UIState,
230254

231255
private setDisabledCategoriesImpl(categories: string[]): void {
232256
this.dispatch(setDisabledCategories(categories));
233-
this.categoryChanged$.emit({ disabledCategories: categories });
257+
// Compute and dispatch hidden items based on disabled categories
258+
const hiddenItems = computeHiddenItems(this.itemCategories, categories);
259+
this.dispatch(setHiddenItems(hiddenItems));
260+
this.categoryChanged$.emit({ disabledCategories: categories, hiddenItems });
234261
}
235262

236263
// ─────────────────────────────────────────────────────────
@@ -268,6 +295,7 @@ export class UIPlugin extends BasePlugin<UIPluginConfig, UICapability, UIState,
268295
setDisabledCategories: (categories) => this.setDisabledCategoriesImpl(categories),
269296
getDisabledCategories: () => this.state.disabledCategories,
270297
isCategoryDisabled: (category) => this.state.disabledCategories.includes(category),
298+
getHiddenItems: () => this.state.hiddenItems,
271299

272300
// Events
273301
onToolbarChanged: this.toolbarChanged$.onGlobal,

packages/plugin-ui/src/lib/utils/consts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const UI_ATTRIBUTES = {
1212
CATEGORIES: 'data-epdf-cat',
1313
/** Disabled categories list on root element */
1414
DISABLED_CATEGORIES: 'data-epdf-dis',
15+
/** Hidden item IDs (computed from disabled categories) */
16+
HIDDEN_ITEMS: 'data-epdf-hid',
1517
} as const;
1618

1719
/**
@@ -23,4 +25,5 @@ export const UI_SELECTORS = {
2325
ITEM: (id: string) => `[${UI_ATTRIBUTES.ITEM}="${id}"]`,
2426
CATEGORIES: (category: string) => `[${UI_ATTRIBUTES.CATEGORIES}~="${category}"]`,
2527
DISABLED_CATEGORY: (category: string) => `[${UI_ATTRIBUTES.DISABLED_CATEGORIES}~="${category}"]`,
28+
HIDDEN_ITEM: (itemId: string) => `[${UI_ATTRIBUTES.HIDDEN_ITEMS}~="${itemId}"]`,
2629
} as const;

0 commit comments

Comments
 (0)