Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions examples/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ regular-layout-frame::part(active-tab) {
}

/* Frame in Overlay Mode */
regular-layout-frame:not([slot]) {
regular-layout-frame.overlay {
background-color: rgba(0, 0, 0, 0.2) !important;
border: 1px dashed rgb(0, 0, 0);
border-radius: 6px;
Expand All @@ -67,7 +67,7 @@ regular-layout-frame::part(container) {
display: none;
}

regular-layout-frame[slot]::part(container) {
regular-layout-frame:not(.overlay)::part(container) {
display: revert;
}

Expand Down
175 changes: 52 additions & 123 deletions src/common/calculate_split.ts → src/common/calculate_edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import { calculate_intersection } from "./calculate_intersect";
import { SPLIT_EDGE_TOLERANCE } from "./constants";
import { insert_child } from "./insert_child";
import {
SPLIT_EDGE_TOLERANCE,
type Layout,
type LayoutPath,
} from "./layout_config";
import type { Layout, LayoutPath, Orientation } from "./layout_config";

/**
* Calculates an insertion point (which may involve splitting a single
Expand All @@ -30,146 +27,78 @@ import {
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
* which is enough to draw the overlay.
*/
function handle_matching_orientation(
export function calculate_edge(
col: number,
row: number,
panel: Layout,
slot: string,
drop_target: LayoutPath,
is_before: boolean,
): LayoutPath {
if (drop_target.path.length === 0) {
const insert_index = is_before ? 0 : 1;
const new_panel = insert_child(panel, slot, [insert_index]);
if (is_before) {
return calculate_intersection(col, row, new_panel, false);
} else {
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
return {
...new_drop_target,
path: [0],
};
}
} else {
const path_without_last = drop_target.path.slice(0, -1);
const last_index = drop_target.path[drop_target.path.length - 1];
const insert_index = is_before ? last_index : last_index + 1;
const new_panel = insert_child(panel, slot, [
...path_without_last,
insert_index,
]);
const is_column_edge =
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE;

if (is_before) {
return calculate_intersection(col, row, new_panel, false);
} else {
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
return {
...new_drop_target,
path: [...path_without_last, last_index],
};
}
const is_row_edge =
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE;

if (is_column_edge) {
return handle_axis(
col,
row,
panel,
slot,
drop_target,
drop_target.column_offset,
"horizontal",
);
} else if (is_row_edge) {
return handle_axis(
col,
row,
panel,
slot,
drop_target,
drop_target.row_offset,
"vertical",
);
}
}

function handle_cross_orientation(
col: number,
row: number,
panel: Layout,
slot: string,
drop_target: LayoutPath,
insert_index: number,
new_orientation: "horizontal" | "vertical",
): LayoutPath {
const original_path = drop_target.path;
const new_panel = insert_child(
panel,
slot,
[...original_path, insert_index],
new_orientation,
);
const new_drop_target = calculate_intersection(col, row, new_panel, false);
return {
...new_drop_target,
slot,
path: [...original_path, insert_index],
};
return drop_target;
}

export function calculate_split(
function handle_axis(
col: number,
row: number,
panel: Layout,
slot: string,
drop_target: LayoutPath,
axis_offset: number,
axis_orientation: Orientation,
): LayoutPath {
const is_column_edge =
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE;
const is_row_edge =
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE;

if (is_column_edge) {
const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE;
if (drop_target.orientation === "horizontal") {
drop_target = handle_matching_orientation(
col,
row,
panel,
slot,
drop_target,
is_before,
);
} else {
const is_before = axis_offset < SPLIT_EDGE_TOLERANCE;
if (drop_target.orientation === axis_orientation) {
if (drop_target.path.length === 0) {
const insert_index = is_before ? 0 : 1;
drop_target = handle_cross_orientation(
col,
row,
panel,
slot,
drop_target,
insert_index,
"horizontal",
);
}

drop_target.is_edge = true;
} else if (is_row_edge) {
const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE;
if (drop_target.orientation === "vertical") {
drop_target = handle_matching_orientation(
col,
row,
panel,
slot,
drop_target,
is_before,
);
const new_panel = insert_child(panel, slot, [insert_index]);
drop_target = calculate_intersection(col, row, new_panel, false);
} else {
const insert_index = is_before ? 0 : 1;
drop_target = handle_cross_orientation(
col,
row,
panel,
slot,
drop_target,
const path_without_last = drop_target.path.slice(0, -1);
const last_index = drop_target.path[drop_target.path.length - 1];
const insert_index = is_before ? last_index : last_index + 1;
const new_panel = insert_child(panel, slot, [
...path_without_last,
insert_index,
"vertical",
);
}
]);

drop_target.is_edge = true;
drop_target = calculate_intersection(col, row, new_panel, false);
}
} else {
const path = [...drop_target.path, is_before ? 0 : 1];
const new_panel = insert_child(panel, slot, path, axis_orientation);
drop_target = calculate_intersection(col, row, new_panel, false);
}

drop_target.is_edge = true;
return drop_target;
}
3 changes: 3 additions & 0 deletions src/common/calculate_intersect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ function calculate_intersection_recursive(
const column_offset =
(column - view_window.col_start) /
(view_window.col_end - view_window.col_start);

const row_offset =
(row - view_window.row_start) /
(view_window.row_end - view_window.row_start);
Expand All @@ -98,6 +99,8 @@ function calculate_intersection_recursive(
path: path,
view_window: view_window,
is_edge: false,
column,
row,
column_offset,
row_offset,
orientation: parent_orientation || "horizontal",
Expand Down
46 changes: 46 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃
// ┃ * of the Regular Layout library, distributed under the terms of the * ┃
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import type { OverlayMode } from "./layout_config";

/**
* The minimum number of pixels the mouse must move to be considered a drag.
*/
export const MIN_DRAG_DISTANCE = 10;

/**
* Class name to use for child elements in overlay position (dragging).
*/
export const OVERLAY_CLASSNAME = "overlay";

/**
* The percentage of the maximum resize distance that will be clamped.
*
*/
export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;

/**
* Threshold from panel edge that is considered a split vs drop action.
*/
export const SPLIT_EDGE_TOLERANCE = 0.25;

/**
* Tolerance threshold for considering two grid track positions as identical.
*
* When collecting and deduplicating track positions, any positions closer than
* this value are treated as the same position to avoid redundant grid tracks.
*/
export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.001;

/**
* The overlay default behavior.
*/
export const OVERLAY_DEFAULT: OverlayMode = "absolute";
1 change: 1 addition & 0 deletions src/common/flatten.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { Layout } from "./layout_config.ts";
*/
export function flatten(layout: Layout): Layout {
if (layout.type === "child-panel") {
layout.selected = layout.selected || 0;
return layout;
}

Expand Down
3 changes: 2 additions & 1 deletion src/common/generate_grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import { GRID_TRACK_COLLAPSE_TOLERANCE, type Layout } from "./layout_config.ts";
import { GRID_TRACK_COLLAPSE_TOLERANCE } from "./constants.ts";
import type { Layout } from "./layout_config.ts";
import { remove_child } from "./remove_child.ts";

interface GridCell {
Expand Down
13 changes: 8 additions & 5 deletions src/common/generate_overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@

import type { LayoutPath } from "./layout_config";

export function updateOverlaySheet({
view_window: { row_start, row_end, col_start, col_end },
box,
}: LayoutPath<DOMRect>) {
export function updateOverlaySheet(
slot: string,
{
view_window: { row_start, row_end, col_start, col_end },
box,
}: LayoutPath<DOMRect>,
) {
const margin = 0;
const top = row_start * box.height + margin / 2;
const left = col_start * box.width + margin / 2;
const height = (row_end - row_start) * box.height - margin;
const width = (col_end - col_start) * box.width - margin;
const css = `position:absolute!important;z-index:1;top:${top}px;left:${left}px;height:${height}px;width:${width}px;`;
return `::slotted(:not([slot])){${css}}`;
return `::slotted([slot="${slot}"]){${css}}`;
}
1 change: 1 addition & 0 deletions src/common/insert_child.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function insert_child(
if (restPath.length === 0 || index === panel.children.length) {
if (is_edge && panel.children[index]?.type === "child-panel") {
panel.children[index].child.unshift(child);
panel.children[index].selected = 0;
return panel;
}

Expand Down
30 changes: 4 additions & 26 deletions src/common/layout_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,10 @@
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

/**
* The percentage of the maximum resize distance that will be clamped.
*
*/
export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;

/**
* Threshold from panel edge that is considered a split vs drop action.
*/
export const SPLIT_EDGE_TOLERANCE = 0.25;

/**
* Tolerance threshold for considering two grid track positions as identical.
*
* When collecting and deduplicating track positions, any positions closer than
* this value are treated as the same position to avoid redundant grid tracks.
*/
export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.001;

/**
* The overlay default behavior.
*/
export const OVERLAY_DEFAULT: OverlayMode = "absolute";

/**
* The overlay behavior type.
*/
export type OverlayMode = "grid" | "absolute" | "interactive";
export type OverlayMode = "grid" | "absolute";

/**
* The representation of a CSS grid, in JSON form.
Expand Down Expand Up @@ -105,6 +81,8 @@ export interface LayoutPath<T = undefined> {
panel: TabLayout;
path: number[];
view_window: ViewWindow;
column: number;
row: number;
column_offset: number;
row_offset: number;
orientation: Orientation;
Expand All @@ -125,7 +103,7 @@ export function* iter_panel_children(panel: Layout): Generator<string> {
yield* iter_panel_children(child);
}
} else {
yield* panel.child;
yield panel.child[panel.selected || 0];
}
}

Expand Down
Loading