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
69 changes: 69 additions & 0 deletions assets/js/live_pane/hooks/pane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ export function createPaneHook() {
expandPane(paneData, groupData);
}
});

this.handleEvent(
'resize',
({ pane_id, size }: { pane_id: string; size: number }) => {
if (paneId === pane_id) {
resizePaneTo(this.el, paneData, groupData, size);
}
}
);
},

updated() {
Expand Down Expand Up @@ -328,6 +337,66 @@ function expandPane(paneData: PaneData, groupData: PaneGroupData) {
groupData.layout.set(nextLayout);
}

function resizePaneTo(
element: HTMLElement,
paneData: PaneData,
groupData: PaneGroupData,
size: number
) {
const numericSize = Number(size);
if (!Number.isFinite(numericSize)) return;

const { minSize = 0, maxSize = 100 } = paneData.constraints;
const clampedSize = Math.min(Math.max(numericSize, minSize), maxSize);

const prevLayout = groupData.layout.get();
const paneDataArray = groupData.paneDataArray.get();

const paneConstraintsArray = paneDataArray.map(pd => pd.constraints);

const { paneSize, pivotIndices } = paneDataHelper(
paneDataArray,
paneData,
prevLayout
);

if (paneSize == null) return;

const isLastPane =
findPaneDataIndex(paneDataArray, paneData.id) === paneDataArray.length - 1;

const delta = isLastPane ? paneSize - clampedSize : clampedSize - paneSize;

const nextLayout = adjustLayoutByDelta({
delta,
layout: prevLayout,
paneConstraintsArray,
pivotIndices,
trigger: 'imperative-api'
});

if (areArraysEqual(prevLayout, nextLayout)) return;

// Detect if this resize causes a collapsed<->expanded state change
const wasCollapsed = isPaneCollapsed(paneDataArray, prevLayout, paneData);
const willBeCollapsed = isPaneCollapsed(paneDataArray, nextLayout, paneData);

if (paneData.constraints.collapsible && wasCollapsed !== willBeCollapsed) {
const transState = willBeCollapsed
? PaneState.Collapsing
: PaneState.Expanding;
handleTransition(
element,
groupData.paneDataArray,
groupData.layout,
paneData,
transState
);
}

groupData.layout.set(nextLayout);
}
Comment thread
kantum marked this conversation as resolved.

function handleTransition(
element: HTMLElement,
paneDataArray: Writable<PaneData[]>,
Expand Down
8 changes: 8 additions & 0 deletions demo/lib/demo_web/components/layouts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ defmodule DemoWeb.Layouts do
Collapsible
</.link>
</li>
<li>
<.link
href={~p"/resize"}
class="-ml-px block border-l pl-4 border-border text-muted-foreground hover:border-primary hover:text-primary"
>
Resize
</.link>
</li>
<li>
<.link
href={~p"/conditional"}
Expand Down
19 changes: 19 additions & 0 deletions demo/lib/demo_web/live/resize_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule DemoWeb.ResizeLive do
use DemoWeb, :live_view

def mount(_params, _session, socket) do
{:ok, assign(socket, size: 50)}
end

def handle_event("update_size", %{"size" => size}, socket) do
case Integer.parse(size) do
{value, _} -> {:noreply, assign(socket, :size, value)}
:error -> {:noreply, socket}
end
end

def handle_event("apply_size", _, socket) do
socket = push_event(socket, "resize", %{pane_id: "resize_pane_1", size: socket.assigns.size})
{:noreply, socket}
end
end
100 changes: 100 additions & 0 deletions demo/lib/demo_web/live/resize_live.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<div class="mx-auto w-full min-w-0 max-w-3xl pt-8">
<header class="prose relative mb-4 max-w-3xl border-b border-border pb-8">
<h1 class="mb-6 scroll-m-20 text-4xl font-bold tracking-tight">Imperative Resize</h1>
<p class="mt-2 text-balance text-lg text-muted-foreground">
Programmatically resize panes from the server using the <code>resize</code> event.
</p>
</header>

<div class="markdown prose relative max-w-3xl pt-4">
<p>
Push a <code>resize</code>
event with <code>pane_id</code>
and <code>size</code>
to resize a pane.
The size is clamped to respect <code>min_size</code>
and <code>max_size</code>
constraints.
</p>

<form phx-change="update_size" class="flex items-center gap-3 my-6">
<input
type="range"
min="10"
max="90"
value={@size}
name="size"
class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
/>
<span class="w-12 text-right font-mono">{@size}%</span>
<button
type="button"
class="inline-flex items-center justify-center whitespace-nowrap rounded-md
text-sm font-medium border transition-colors shadow-sm h-9 px-4
!bg-zinc-50 !border-zinc-200/50 !text-brand hover:!bg-zinc-100"
phx-click="apply_size"
>
Apply
</button>
</form>

<LivePane.group id="resize_demo" class="h-[450px] border-2 border-gray-300 rounded-lg">
<LivePane.pane
min_size={10}
max_size={90}
starting_size={50}
group_id="resize_demo"
id="resize_pane_1"
class="h-full"
>
<div class="flex h-full items-center justify-center rounded-lg bg-gray-100 p-6">
<span class="font-semibold">Resizable Pane</span>
</div>
</LivePane.pane>

<LivePane.resizer
id="resize_demo-resizer"
group_id="resize_demo"
class="relative flex h-full w-2 items-center justify-center"
>
<div class="z-10 h-7 flex items-center w-4 rounded-sm border bg-brand">
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
fill="currentColor"
viewBox="0 0 256 256"
class="size-4 text-black"
>
<rect width="256" height="256" fill="none"></rect>
<path d="M108,60A16,16,0,1,1,92,44,16,16,0,0,1,108,60Zm56,16a16,16,0,1,0-16-16A16,16,0,0,0,164,76ZM92,112a16,16,0,1,0,16,16A16,16,0,0,0,92,112Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,112ZM92,180a16,16,0,1,0,16,16A16,16,0,0,0,92,180Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,180Z">
</path>
</svg>
</div>
</LivePane.resizer>
<LivePane.pane starting_size={50} group_id="resize_demo" id="resize_pane_2" class="h-full">
<div class="flex h-full items-center justify-center rounded-lg bg-gray-100 p-6">
<span class="font-semibold">Other Pane</span>
</div>
</LivePane.pane>
</LivePane.group>

<h2 class="mt-12 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0">Usage</h2>

<div id="anatomy" phx-hook="Highlight" phx-update="ignore" class="bg-inherit">
<pre class="p-0 bg-inherit">
<code class="language-elixir p-0" id="code">
<%="""
def handle_event(\"resize_pane\", _, socket) do
socket = push_event(socket, \"resize\", %{
pane_id: \"my_pane_id\",
size: 30
})
{:noreply, socket}
end
"""%>
</code>
</pre>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions demo/lib/demo_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ defmodule DemoWeb.Router do
get "/overflowing", PageController, :overflowing

live "/collapsible", CollapsibleLive, :collapsible
live "/resize", ResizeLive, :resize
live "/conditional", ConditionalLive, :conditional
live "/states", StatesLive, :states
live "/updating_content", UpdatingContentLive, :updating_content
Expand Down
24 changes: 24 additions & 0 deletions lib/live_pane.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ defmodule LivePane do
<div>Pane 2</div>
</LivePane.pane>
</LivePane.group>

## Server-side Events

Pane hooks listen to the following events that can be pushed from the server:

### collapse

Collapses a collapsible pane to its `collapsed_size`.

push_event(socket, "collapse", %{pane_id: "my_pane_id"})

### expand

Expands a collapsed pane to its previous size (or `min_size` if no previous size).

push_event(socket, "expand", %{pane_id: "my_pane_id"})

### resize

Resizes a pane to a specific size (as a percentage of the group).

push_event(socket, "resize", %{pane_id: "my_pane_id", size: 30})

The size will be automatically clamped to respect the pane's `min_size` and `max_size` constraints.
"""
use Phoenix.Component

Expand Down