Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2e6aa3e
Divider fix for overflow
ValentinaKozlova Jun 12, 2023
e182d0f
Change files
ValentinaKozlova Jun 13, 2023
b65b8ce
fix for overflow in Breadcrumb and api regenerated
ValentinaKozlova Jun 13, 2023
0f26cfa
fixed test and api generation
ValentinaKozlova Jun 14, 2023
179cbfb
build fail fix
ValentinaKozlova Jun 14, 2023
f4a096c
Update change/@fluentui-priority-overflow-8e128a48-5402-4886-b088-900…
ValentinaKozlova Jun 15, 2023
7ab96e6
Update change/@fluentui-priority-overflow-8e128a48-5402-4886-b088-900…
ValentinaKozlova Jun 15, 2023
ba88e36
Update change/@fluentui-react-components-7b21b68d-a82b-4ee6-802c-cdbb…
ValentinaKozlova Jun 15, 2023
e066826
Update change/@fluentui-react-components-7b21b68d-a82b-4ee6-802c-cdbb…
ValentinaKozlova Jun 15, 2023
894c4b6
PR fix
ValentinaKozlova Jun 15, 2023
09869c8
PR fix #2
ValentinaKozlova Jun 15, 2023
77039fd
Update packages/react-components/react-overflow/src/components/Overfl…
ValentinaKozlova Jun 15, 2023
ac06836
Update packages/react-components/react-overflow/src/components/Overfl…
ValentinaKozlova Jun 15, 2023
27cf866
regenerated api
ValentinaKozlova Jun 16, 2023
465c4b9
Update packages/react-components/react-overflow/stories/Overflow/Larg…
ValentinaKozlova Jun 16, 2023
1b4a2eb
Update packages/react-components/react-overflow/stories/Overflow/Larg…
ValentinaKozlova Jun 16, 2023
38d6dcc
Update packages/react-components/react-overflow/stories/Overflow/Larg…
ValentinaKozlova Jun 16, 2023
9dff18a
Update packages/react-components/priority-overflow/src/overflowManage…
ValentinaKozlova Jun 16, 2023
1ae6d1f
pr fix#3
ValentinaKozlova Jun 16, 2023
cbf1d8a
fix
ValentinaKozlova Jun 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Added support for custom divider",
"packageName": "@fluentui/priority-overflow",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: Added Overflow divider",
"packageName": "@fluentui/react-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Added support for custom divider",
"packageName": "@fluentui/react-overflow",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export type OverflowAxis = 'horizontal' | 'vertical';
// @public (undocumented)
export type OverflowDirection = 'start' | 'end';

// @public (undocumented)
export interface OverflowDividerEntry {
element: HTMLElement;
// (undocumented)
groupId: string;
}

// @public
export interface OverflowEventPayload {
// (undocumented)
Expand All @@ -61,11 +68,13 @@ export interface OverflowItemEntry {

// @internal (undocumented)
export interface OverflowManager {
addDivider: (divider: OverflowDividerEntry) => void;
addItem: (items: OverflowItemEntry) => void;
addOverflowMenu: (element: HTMLElement) => void;
disconnect: () => void;
forceUpdate: () => void;
observe: (container: HTMLElement, options: ObserveOptions) => void;
removeDivider: (groupId: string) => void;
removeItem: (itemId: string) => void;
removeOverflowMenu: () => void;
update: () => void;
Expand Down
2 changes: 2 additions & 0 deletions packages/react-components/priority-overflow/src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DATA_OVERFLOWING = 'data-overflowing';
export const DATA_OVERFLOW_GROUP = 'data-overflow-group';
1 change: 1 addition & 0 deletions packages/react-components/priority-overflow/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export type {
OverflowEventPayload,
OverflowGroupState,
OverflowItemEntry,
OverflowDividerEntry,
OverflowManager,
} from './types';
207 changes: 140 additions & 67 deletions packages/react-components/priority-overflow/src/overflowManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { DATA_OVERFLOWING, DATA_OVERFLOW_GROUP } from './consts';
import { debounce } from './debounce';
import { createPriorityQueue } from './priorityQueue';
import type { OverflowGroupState, OverflowItemEntry, OverflowManager, ObserveOptions } from './types';
import { createPriorityQueue, PriorityQueue } from './priorityQueue';
import type {
OverflowGroupState,
OverflowItemEntry,
OverflowManager,
ObserveOptions,
OverflowDividerEntry,
} from './types';

/**
* @internal
Expand All @@ -24,7 +31,7 @@ export function createOverflowManager(): OverflowManager {
};

const overflowItems: Record<string, OverflowItemEntry> = {};
const overflowGroups: Record<string, { visibleItemIds: Set<string>; invisibleItemIds: Set<string> }> = {};
const overflowDividers: Record<string, OverflowDividerEntry> = {};
const resizeObserver = new ResizeObserver(entries => {
if (!entries[0] || !container) {
return;
Expand All @@ -33,6 +40,66 @@ export function createOverflowManager(): OverflowManager {
update();
});

const getNextItem = (queueToDequeue: PriorityQueue<string>, queueToEnqueue: PriorityQueue<string>) => {
const nextItem = queueToDequeue.dequeue();
queueToEnqueue.enqueue(nextItem);
return overflowItems[nextItem];
};

const createGroupManager = () => {
const groupVisibility: Record<string, OverflowGroupState> = {};
const groups: Record<string, { visibleItemIds: Set<string>; invisibleItemIds: Set<string> }> = {};
function updateGroupVisibility(groupId: string) {
const group = groups[groupId];
if (group.invisibleItemIds.size && group.visibleItemIds.size) {
groupVisibility[groupId] = 'overflow';
} else if (group.visibleItemIds.size === 0) {
groupVisibility[groupId] = 'hidden';
} else {
groupVisibility[groupId] = 'visible';
}
}
function isGroupVisible(groupId: string) {
return groupVisibility[groupId] === 'visible' || groupVisibility[groupId] === 'overflow';
}
return {
groupVisibility: () => groupVisibility,
isSingleItemVisible(itemId: string, groupId: string) {
return (
isGroupVisible(groupId) &&
groups[groupId].visibleItemIds.has(itemId) &&
groups[groupId].visibleItemIds.size === 1
);
},
addItem(itemId: string, groupId: string) {
groups[groupId] ??= {
visibleItemIds: new Set<string>(),
invisibleItemIds: new Set<string>(),
};

groups[groupId].visibleItemIds.add(itemId);
updateGroupVisibility(groupId);
},
removeItem(itemId: string, groupId: string) {
groups[groupId].invisibleItemIds.delete(itemId);
groups[groupId].visibleItemIds.delete(itemId);
updateGroupVisibility(groupId);
},
showItem(itemId: string, groupId: string) {
groups[groupId].invisibleItemIds.delete(itemId);
groups[groupId].visibleItemIds.add(itemId);
updateGroupVisibility(groupId);
},
hideItem(itemId: string, groupId: string) {
groups[groupId].invisibleItemIds.add(itemId);
groups[groupId].visibleItemIds.delete(itemId);
updateGroupVisibility(groupId);
},
};
};

const groupManager = createGroupManager();

const invisibleItemQueue = createPriorityQueue<string>((a, b) => {
const itemA = overflowItems[a];
const itemB = overflowItems[b];
Expand Down Expand Up @@ -72,30 +139,41 @@ export function createOverflowManager(): OverflowManager {
return options.overflowAxis === 'horizontal' ? el.offsetWidth : el.offsetHeight;
};

const makeItemVisible = () => {
const nextVisible = invisibleItemQueue.dequeue();
visibleItemQueue.enqueue(nextVisible);
function computeSizeChange(entry: OverflowItemEntry) {
const dividerWidth =
entry.groupId && groupManager.isSingleItemVisible(entry.id, entry.groupId) && overflowDividers[entry.groupId]
? getOffsetSize(overflowDividers[entry.groupId].element)
: 0;

return getOffsetSize(entry.element) + dividerWidth;
}

const item = overflowItems[nextVisible];
const showItem = () => {
const item = getNextItem(invisibleItemQueue, visibleItemQueue);
options.onUpdateItemVisibility({ item, visible: true });

if (item.groupId) {
overflowGroups[item.groupId].invisibleItemIds.delete(item.id);
overflowGroups[item.groupId].visibleItemIds.add(item.id);
groupManager.showItem(item.id, item.groupId);

if (groupManager.isSingleItemVisible(item.id, item.groupId)) {
overflowDividers[item.groupId]?.element.removeAttribute(DATA_OVERFLOWING);
}
}

return getOffsetSize(item.element);
return computeSizeChange(item);
};

const makeItemInvisible = () => {
const nextInvisible = visibleItemQueue.dequeue();
invisibleItemQueue.enqueue(nextInvisible);

const item = overflowItems[nextInvisible];
const width = getOffsetSize(item.element);
const hideItem = () => {
const item = getNextItem(visibleItemQueue, invisibleItemQueue);
const width = computeSizeChange(item);
options.onUpdateItemVisibility({ item, visible: false });

if (item.groupId) {
overflowGroups[item.groupId].visibleItemIds.delete(item.id);
overflowGroups[item.groupId].invisibleItemIds.add(item.id);
if (groupManager.isSingleItemVisible(item.id, item.groupId)) {
overflowDividers[item.groupId]?.element.setAttribute(DATA_OVERFLOWING, '');
}

groupManager.hideItem(item.id, item.groupId);
}

return width;
Expand All @@ -108,66 +186,45 @@ export function createOverflowManager(): OverflowManager {
const visibleItems = visibleItemIds.map(itemId => overflowItems[itemId]);
const invisibleItems = invisibleItemIds.map(itemId => overflowItems[itemId]);

const groupVisibility: Record<string, OverflowGroupState> = {};
Object.entries(overflowGroups).forEach(([groupId, groupState]) => {
if (groupState.invisibleItemIds.size && groupState.visibleItemIds.size) {
groupVisibility[groupId] = 'overflow';
} else if (groupState.visibleItemIds.size === 0) {
groupVisibility[groupId] = 'hidden';
} else {
groupVisibility[groupId] = 'visible';
}
});

options.onUpdateOverflow({ visibleItems, invisibleItems, groupVisibility });
options.onUpdateOverflow({ visibleItems, invisibleItems, groupVisibility: groupManager.groupVisibility() });
};

const processOverflowItems = (): boolean => {
if (!container) {
return false;
}
const totalDividersWidth = Object.values(overflowDividers)
.map(dvdr => (dvdr.groupId ? getOffsetSize(dvdr.element) : 0))
.reduce((prev, current) => prev + current, 0);

const availableSize = getOffsetSize(container) - options.padding;
const overflowMenuOffset = overflowMenu ? getOffsetSize(overflowMenu) : 0;
const availableSize =
getOffsetSize(container) -
options.padding -
totalDividersWidth -
(overflowMenu ? getOffsetSize(overflowMenu) : 0);

// Snapshot of the visible/invisible state to compare for updates
const visibleTop = visibleItemQueue.peek();
const invisibleTop = invisibleItemQueue.peek();

const visibleItemIds = visibleItemQueue.all();
let currentWidth = visibleItemIds.reduce((sum, visibleItemId) => {
const child = overflowItems[visibleItemId].element;
return sum + getOffsetSize(child);
}, 0);
let currentWidth = visibleItemQueue
.all()
.map(id => overflowItems[id].element)
.map(getOffsetSize)
.reduce((prev, current) => prev + current, 0);

// Add items until available width is filled - can result in overflow
while (currentWidth < availableSize && invisibleItemQueue.size() > 0) {
currentWidth += makeItemVisible();
currentWidth += showItem();
}

// Remove items until there's no more overflow
while (currentWidth > availableSize && visibleItemQueue.size() > 0) {
if (visibleItemQueue.size() <= options.minimumVisible) {
break;
}
currentWidth -= makeItemInvisible();
}

// make sure the overflow menu can fit
if (
visibleItemQueue.size() > options.minimumVisible &&
invisibleItemQueue.size() > 0 &&
currentWidth + overflowMenuOffset > availableSize
) {
makeItemInvisible();
while (currentWidth > availableSize && visibleItemQueue.size() > options.minimumVisible) {
currentWidth -= hideItem();
}

// only update when the state of visible/invisible items has changed
if (visibleItemQueue.peek() !== visibleTop || invisibleItemQueue.peek() !== invisibleTop) {
return true;
}

return false;
return visibleItemQueue.peek() !== visibleTop || invisibleItemQueue.peek() !== invisibleTop;
};

const forceUpdate: OverflowManager['forceUpdate'] = () => {
Expand Down Expand Up @@ -210,14 +267,8 @@ export function createOverflowManager(): OverflowManager {
}

if (item.groupId) {
if (!overflowGroups[item.groupId]) {
overflowGroups[item.groupId] = {
visibleItemIds: new Set<string>(),
invisibleItemIds: new Set<string>(),
};
}

overflowGroups[item.groupId].visibleItemIds.add(item.id);
groupManager.addItem(item.id, item.groupId);
item.element.setAttribute(DATA_OVERFLOW_GROUP, item.groupId);
}

update();
Expand All @@ -227,10 +278,30 @@ export function createOverflowManager(): OverflowManager {
overflowMenu = el;
};

const addDivider: OverflowManager['addDivider'] = divider => {
if (!divider.groupId || overflowDividers[divider.groupId]) {
return;
}

divider.element.setAttribute(DATA_OVERFLOW_GROUP, divider.groupId);
overflowDividers[divider.groupId] = divider;
};

const removeOverflowMenu: OverflowManager['removeOverflowMenu'] = () => {
overflowMenu = undefined;
};

const removeDivider: OverflowManager['removeDivider'] = groupId => {
if (!overflowDividers[groupId]) {
return;
}
const divider = overflowDividers[groupId];
if (divider.groupId) {
delete overflowDividers[groupId];
divider.element.removeAttribute(DATA_OVERFLOW_GROUP);
}
};

const removeItem: OverflowManager['removeItem'] = itemId => {
if (!overflowItems[itemId]) {
return;
Expand All @@ -241,8 +312,8 @@ export function createOverflowManager(): OverflowManager {
invisibleItemQueue.remove(itemId);

if (item.groupId) {
overflowGroups[item.groupId].visibleItemIds.delete(item.id);
overflowGroups[item.groupId].invisibleItemIds.delete(item.id);
groupManager.removeItem(item.id, item.groupId);
item.element.removeAttribute(DATA_OVERFLOW_GROUP);
}

delete overflowItems[itemId];
Expand All @@ -258,5 +329,7 @@ export function createOverflowManager(): OverflowManager {
update,
addOverflowMenu,
removeOverflowMenu,
addDivider,
removeDivider,
};
}
19 changes: 19 additions & 0 deletions packages/react-components/priority-overflow/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ export interface OverflowItemEntry {
groupId?: string;
}

export interface OverflowDividerEntry {
/**
* HTML element that will be disappear when overflowed
*/
element: HTMLElement;

groupId: string;
}

/**
* signature similar to standard event listeners, but typed to handle the custom event
*/
Expand Down Expand Up @@ -111,6 +120,16 @@ export interface OverflowManager {
*/
addOverflowMenu: (element: HTMLElement) => void;

/**
* Add overflow divider
*/
addDivider: (divider: OverflowDividerEntry) => void;

/**
* Remove overflow divider
*/
removeDivider: (groupId: string) => void;

/**
* Unsets the overflow menu element
*/
Expand Down
Loading