-
Notifications
You must be signed in to change notification settings - Fork 6
Tile Manager Specification
- Tile Manager Specification
Team name: CodeX
Developer names: Maria Tsvyatkova, Ivaylo Barakov
Designer name: Silvia Ivanova
- Damyan Petev
- Svilen Dimchevski
- Radoslav Karaivanov
- Radoslav Mirchev
Version | Author | Date | Notes |
---|---|---|---|
1 | Maria Tsvyatkova | 2024-09-19 | Initial draft |
1.1 | Maria Tsvyatkova | 2024-10-09 | Updated user stories |
1.2 | Ivaylo Barakov | 2025-02-03 | Update Functionality with resizing draft |
1.3 | Galina Edinakova | 2025-02-07 | IgcTileManagerComponent API update |
1.4 | Radoslav Karaivanov | 2025-02-27 | Updated with latest implementation changes |
1.5 | Radoslav Karaivanov | 2025-03-13 | Dropped explicit grid template rows |
The igc-tile-manager component enables developers to layout different content inside of tile containers. The tile manager will try to position and arrange those tile containers based on either user provided configuration or its default one. Additional features such a tile drag rearrangement, resizing of tiles, maximized and fullscreen states can be enabled for users to interact with.
Typical use-cases for the tile manager are product and image galleries, dashboards for reports or metrics and bespoke UI scenarios.
The tile manager provides a base tile layout behavior based on the native CSS Grid specification. Tiles accept slotted content and the manager will arrange them based on its and their configuration. Each tile can be configured and sized independently of each other. Features such as rearranging and resizing tiles are also available after configuration. Additional states such as maximized and fullscreen mode can be enabled/disabled per tile, with a default UI that can be customized.
The igc-tile-manager
component must:
- support rendering user-provided tiles and content inside those tiles.
- expose an API to configure the underlying CSS Grid in order to accommodate specific user scenarios.
- expose an API to enable dragging and rearrangement of tiles.
- expose and API to enable resizing of tiles.
- expose an API to serialize the configuration of its tiles into a standard JSON format, so it can be saved either locally or remotely.
- expose an API to deserialize a previously exported configuration and apply it to its current tile descendants.
- follow WAI-ARIA best practices.
The igc-tile
component must:
- expose an API to configure its initial position and layout inside the tile manager.
- support customization of the its content and DOM parts. Users must be able to customize different parts of the tile (header, action buttons, etc) with slots.
- provide a default UI for maximized and fullscreen states as well as expose API to control those states and modify the default UI associated with them.
- participate in tile drag rearrangement when configured by the tile manager.
- participate in tile resizing when configured by the tile manager.
- expose an API to disable resize behavior per tile.
As an end-user I expect to be able to:
- resize a tile by dragging its corner, in order to better view its content or highlight the importance of its content.
- reorder tiles via drag and drop, in order to control the layout and information flow in the tile manager.
- see a preview of a drag and drop operation, in order to understand the impact of placing the tile at the position.
- maximize a tile, in order to focus attention on its content.
- have the tiles inside the tile manager rearrange themselves when the tile manager or the viewport dimensions are changed so I can have a responsive view of the content.
As a developer I expect to be able to:
- declaratively define tiles and their content inside the tile manager.
- define the number of columns, so that I can control the maximum number of tiles which can be displayed in a given row.
- specify how many rows and columns a tile should span, so that I can display tiles with different sizes.
- have an option to allow tile dragging only by their header, so that I can let users interact with the tile's content.
- have an option to allow tile dragging by initiating a drag operation on the tile itself (header + content).
- add/remove tile with different sizes dynamically and the tiles should rearrange minimizing the empty space between them, so that I can have a compact layout.
- save the current layout, so that I can reload it when needed.
- restrict the end user from resizing a specific tile, so that I can ensure it is always displayed as I intended.
- have a completely customizable tile header, so that I can modify its content based on my application logic.
- expose a mode where the layout has a maximized tile and a list of minimized tiles beside it.
- set the size and position of the maximized tile and minimized tiles list, so that I can control the arrangement of tiles in the layout. Default maximized left.
- define the content displayed in each tile based on its current state, so that I can customize the appearance of tiles in minimized, minimized-expanded, maximized, and normal states.
- use a custom style for the tile drag preview, so that I can provide a customized view of a tile while being dragged.
- be able to drill into a minimized tile without making it the maximized tile, so that I can see tile's content without changing the currently maximized tile. (This is applicable if we have both minimized and minimized-expanded states)
The tile manager wraps its projected tile components into an internal CSS grid based container. The tile manager default slot projects only igc-tile components directly slotted inside it. Any other DOM elements will be ignored and not rendered by the tile manager.
Layout in the tile manager is configured by the properties of the component itself combined with the configuration of its igc-tile children.
Dragging behavior in the tile manager is enabled by setting the dragMode property ot one of the following values:
- none - Dragging of tiles is disabled.
- tile-header - Dragging is initiated by a click inside the title slot of the tile header.
- tile - Dragging is initiated by a click inside the tile.
Important
Since both drag & resize operations are based on the native PointerEvent API, in order to keep user interaction consistent between resize and drag, when a click is initiated on a resize adorner it will take precedence over the dragging behavior.
Dragging is also ignored on pointer interaction with any elements inside the tile actions slot which includes the default UI for maximize and fullscreen as well as any user provided content.
When initiating a drag operation a ghost copy of the dragged tile is created while the original element is transitioned into a drag state. While dragging the ghost copy around the tile manager for each pointer enter event into a tile, the original copy will switch position and relevant properties (colStart, rowStart) with the touched tile.
This will be valid for all tiles along the path of the drag operation and since the CSS Grid container is set to dense there could be general shift between the a group of tiles if the dragged tile cannot fit into the new position.
- While the tile is in maximized or fullscreen states, the tile cannot be dragged.
Resizing in the tile manager is a functionality that allows tiles to be resized using three different resize adorners.
- Side Adorner - Adjusts width by modifying the column span.
- Bottom Adorner - Adjusts height by modifying the row span.
- Corner Adorner - Adjusts both width and height simultaneously.
To achieve smooth resizing, a ghost element is used instead of directly modifying the tile's dimensions. It appears on top of the original tile with it's current dimensions when resizing starts and updates in real time as the user drags any of the resize handles. This approach is known as deferred resizing, meaning that no changes are applied to the actual tile until resizing is complete.
Tiles always resize in alignment with the CSS grid, meaning they can only expand or shrink in full grid units. When resizing horizontally or vertically, the ghost will snap to the next column or row once it reaches the halfway point of that unit. If the resize remains below the halfway threshold when resizing is completed, the tile maintains its current span. This prevents partial resizing and ensures that tiles conform to the grid's structure.
Additionally, grid gaps (grid-gap values) are taken into account during resizing, ensuring that the spacing between tiles remains consistent.
Once the user releases the resize handle, the final grid-row and grid-column spans are calculated based on the ghost's position and size. At this point, the ghost element is removed, and the actual tile is updated to reflect its new size.
Note
If the ghost exceeds the available grid space, it will be resized to the largest possible span within the grid's limits.
The CSS grid automatically rearranges itself when a tile changes size, ensuring that there is minimal empty space. Thats why expanding a tile may push adjacent tiles into new positions, while shrinking creates gaps that other tiles may fill dynamically. This ensures that the tile manager remains as compact as possible without any overlapping tiles and all movements remain within the defined grid structure.
The resize process emits three key events corresponding to pointer interactions:
- igcResizeStart - fired on pointerdown when the user initiates resizing by clicking any of the resize handles
- igcResize - fired on pointermove, continuously updating the ghost element’s size as the user resizes
- igcResizeEnd - fired on lostpointercapture when resizing ends, applying the new dimensions to the tile and removing the ghost element
- igcResizeCancel - fired on pressing Escape during a resize operation. Doing so will cancel the current resize operation, remove the ghost element and leave the state of the tile as it was before the start of the operation.
- Developers can override a tile resize behavior by setting disableResizing to true.
- While the tile is in maximized or fullscreen states, the tile cannot be resized.
- The resize adorners of the tile can be customized through their slots. See the API table below for information.
There are several constraints and limitations in the resizing process:
- A tile cannot be resized smaller than the tile manager's defined minimum column and row values (
minColWidth
,minRowHeight
) - A tile cannot be resized beyond the maximum available space in the CSS grid. If a tile starts at a certain column/row and the user attempts to resize it beyond the visible grid area, it will only expand up to the maximum available columns/rows from its starting position.
Note
E.g. if there are 7 columns and a tile starts at column 4, resizing it to 2000px width will not extend beyond column 7, as that is the maximum available space.
No specific implementation details are required. Tiles and their content are user controlled, so localization handling is left to the user as an application scenario.
Note
This may not be entirely true, since the tile headers have default icon-buttons which should have an aria-label.
Key | Description |
---|---|
Escape | Applicable only during drag and resize interactions. Cancels the current interaction. |
- Pressing Escape during resize will cancel the current resize operation, remove the ghost element and leave the state of the tile as it was before the start of the operation.
- Pressing Escape during drag will unwind all the rearranged tiles during the operation back to their initial state and remove the ghost element.
Property | Attribute | Reflected | Property Type | Default | Description |
---|---|---|---|---|---|
columnCount | column-count | No | number | 0 | Sets the number of columns in the tile manager. Settings this to 0 or a negative value |
minColumnWidth | min-column-width | No | string | - | Sets the minimum width for a column in the tile manager |
minRowHeight | min-row-height | No | string | - | Sets the minimum height for a row in the tile manager |
gap | gap | No | string | - | Sets the CSS Grid gap size |
tiles | - | No | readonly Array<IgcTileComponent> | - | Get the underlying tiles sorted by their position |
dragMode | drag-mode | No | none | tile-header | tile | none | Sets whether drag operations are enabled and the type of drag operations behavior |
resizeMode | resize-mode | No | none | hover | always | none | Sets whether resize operations are enabled and the behavior of resize adorners for tiles |
Name | Type signature | Description |
---|---|---|
saveLayout | (): string | Returns the current tiles layout configuration an properties serialized as a JSON string |
loadLayout | (data: string): void | Loads a previously serialized configuration and attempts to restore the state to the current tiles |
Name | Description |
---|---|
(default) | Default slot for the tile manager. Only igc-tile elements will be projected inside the CSS grid container |
Name | Description |
---|---|
base | The tile manager internal CSS Grid container |
Name | Description |
---|---|
--column-count | The number of columns for the tile manager. The column-count attribute sets this variable |
--min-col-width | The minimum size of the columns in the tile-manager. The min-column-width attribute sets this variable |
--min-row-height | The minimum size of the rows in the tile-manager. The min-row-height attribute sets this variable |
--grid-gap | The gap size of the underlying CSS grid container. The gap attributes sts this variable |
Property | Attribute | Reflected | Property Type | Default | Description |
---|---|---|---|---|---|
colStart | col-start | No | number | - | The column in the CSS Grid container where the tile will start. If not set, it implicitly fallbacks to 'auto' |
colSpan | col-span | No | number | 1 | The number of CSS columns the tile will span. Setting values which are less than 1 are rejected and coerced to 1 |
rowStart | row-start | No | number | - | The row in the CSS Grid container where the tile will start. If not set, it implicitly fallbacks to 'auto' |
rowSpan | row-span | No | number | 1 | The number of CSS rows the tile will span. Setting values which are less than 1 are rejected and coerced to 1 |
position | position | No | number | -1 | Controls the visual position of the tile inside the manager layout |
disableFullscreen | disable-fullscreen | Yes | boolean | false | When set hides the default fullscreen action button |
disableMaximize | disable-maximize | Yes | boolean | false | When set hides the default maximize toggle action button |
disableResize | disable-resize | Yes | boolean | false | When set, resize behavior is disabled for the tile regardless of the parent tile manager configuration |
fullscreen | - | - | readonly boolean | false | Whether the tile current state is fullscreen |
Name | Cancellable | Parameters | Description |
---|---|---|---|
igcTileDragStart | true | IgcTileComponent | Emitted when a drag operation is initiated |
igcTileDragEnd | false | IgcTileComponent | Emitted when a drag operation is completed |
igcTileDragCancel | false | IgcTileComponent | Emitted when a drag operation is discarded |
igcTileResizeStart | true | IgcTileComponent | Emitted when a resize operation is initiated |
igcTileResizeEnd | false | IgcTileComponent | Emitted when a resize operation is completed |
igcTileResizeCancel | false | IgcTileComponent | Emitted when a resize operation is discarded |
igcTileFullscreen | true | { IgcTileComponent, state: boolean} | Emitted when a tile is entering into fullscreen state through the default tile header UI |
igcTileMaximized | true | { IgcTileComponent state: boolean } | Emitted a a tile maximized state is toggled through the default tile header UI |
Name | Description |
---|---|
(default) | Default slot for slotting content into the tile |
title | Content for the tile header |
fullscreen-action | Overwrite the default fullscreen action content |
maximize-action | Overwrite the default maximize action content |
actions | Custom content rendered after the default actions |
side-adorner | Overwrite the default horizontal resize adorner |
corner-adorner | Overwrite the default diagonal resize adorner |
bottom-adorner | Overwrite the default vertical resize adorner |
Name | Description |
---|---|
base | The wrapper for the entire tile content, header and content |
header | The container for the tile header, including title and actions |
title | The title container of the tile |
actions | The actions container of the tile header |
content-container | The container wrapping the tile’s main content |
trigger-side | The part for the side adorner of the encapsulated resize element in the tile |
trigger | The part for the corner adorner of the encapsulated resize element in the tile |
trigger-bottom | The part for the bottom adorner of the encapsulated resize element in the tile |
Name | Description |
---|
- is defined and rendered
- is initialized with proper default state
- passes the default WAI-ARIA automated tests
- accepts and renders only slotted
igc-tile
component(s) - correct rendering state of slotted tiles
- correct rendering state of slotted content inside a tile
- correct rendering state of slotted content inside the tile header
- DOM and internal state are correct when a tile is added dynamically
- DOM and internal state are correct when a tile is removed dynamically
- correctly serializes tiles collection properties to a JSON string
- correctly deserializes a serialized tiles collection and assign back the properties to the tiles
- when a smaller tile is dragged over a larger tile, the tiles swap positions only once and do not trigger additional swaps as long as the pointer remains over the larger tile and the movement direction does not change
In general the tile manager and the tile components do not describe explicit ARIA roles.
There is a limitation around the use of the CSS order property which means that the viewport layout may differ from the DOM hierarchy. In that case default keyboard focus behavior may seem weird since the position in the viewport could be different from the actual DOM hierarchy position. The same limitation is applicable to the default dense layout of the underlying CSS grid container, since the algorithm will try to fill any "holes" in the grid, leading to items not appearing "in order".
Both the tile manager and its tiles should work in RTL context without additional configuration or disruption of behavior.
- Fixed column count constraints
When the tile manager is configured with a specific columnCount
, each tile’s columnStart
and columnSpan
are validated to ensure that tiles fit within the defined columns and do not create empty areas. Any values exceeding the available grid columns are clamped to the maximum allowable range and the tile’s properties always reflect these adjusted values.
-
Example: Excessive
columnSpan
If
columnCount
is 5 and a tile has acolumnSpan
set to 10, the tile is limited to 5 columns. The tile’scolumnSpan
property will return 5 instead of the originally requested 10. -
Example: Valid
colummnStart
with excessivecolumnSpan
When a tile’s
columnStart
is valid (i.e. it is less than the total number of columns), but the specifiedcolumnSpan
extends past the grid’s columns, the tile will again be clamped to fit. For instance, ifcolumnStart = 3
andcolumnSpan = 5
, the tile will occupy a total of 3 columns -
Example: Excessive
columnStart
If the tile’s
columnStart
exceeds the total number of columns (e.g.,columnStart = 10
), the tile manager resets this value to 0, placing the tile at the start of the next row. AnycolumnSpan
specified for that tile is then evaluated and clamped as necessary (up to a maximum of 5 columns in this example). Consequently, the returnedcolumnStart
will be 0 instead of 10.
These checks guarantee that tiles always fit within the defined layout, preventing gaps, invalid spans or empty columns.
- Drag and resize behaviors are using the ViewTransition API for added UX value. Currently only Firefox and Firefox derivatives do not implement the feature fully, meaning there will be no transitions in those browsers.
- The default behavior of drag operations is to "insert" the dragged tile between the currently targeted tile and any
tiles before it. Since the manager tries to keep tiles packed as much as it can, depending on the current size of the dragged tile and the target, one of
these scenarios is a possibility:
- the tile is moved but shifts a chunk of other tiles with it since this will be the optimal layout for the new state.
- no layout rearrangement happens, since the new layout is seen as invalid
Note
This could be mitigated when another drag operation (e.g. swap) is introduced which is in the roadmap for future enhancements.