Fix Gap and Modal Position
This commit is contained in:
parent
43a140c08d
commit
6d969944dc
62
examples/gap_test.php
Normal file
62
examples/gap_test.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Ui\Widget\Button;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
use PHPNative\Ui\Window;
|
||||
|
||||
$app = new Application();
|
||||
$window = new Window('Gap Test', 600, 400);
|
||||
|
||||
// Main container
|
||||
$mainContainer = new Container('flex flex-col p-4 bg-gray-100 gap-4');
|
||||
|
||||
// Test 1: Row with gap-2
|
||||
$test1 = new Container('flex flex-col gap-2');
|
||||
$test1->addComponent(new \PHPNative\Ui\Widget\Label('Test 1: flex-row gap-2', 'text-lg font-bold'));
|
||||
$row1 = new Container('flex flex-row gap-2 bg-white p-4');
|
||||
$row1->addComponent(new Button('Button 1', 'px-4 py-2 bg-blue-600 text-white rounded'));
|
||||
$row1->addComponent(new Button('Button 2', 'px-4 py-2 bg-green-600 text-white rounded'));
|
||||
$test1->addComponent($row1);
|
||||
$mainContainer->addComponent($test1);
|
||||
|
||||
// Test 2: Row with gap-4
|
||||
$test2 = new Container('flex flex-col gap-2');
|
||||
$test2->addComponent(new \PHPNative\Ui\Widget\Label('Test 2: flex-row gap-4', 'text-lg font-bold'));
|
||||
$row2 = new Container('flex flex-row gap-4 bg-white p-4');
|
||||
$row2->addComponent(new Button('Button A', 'px-4 py-2 bg-red-600 text-white rounded'));
|
||||
$row2->addComponent(new Button('Button B', 'px-4 py-2 bg-purple-600 text-white rounded'));
|
||||
$test2->addComponent($row2);
|
||||
$mainContainer->addComponent($test2);
|
||||
|
||||
// Test 3: Row with gap-8
|
||||
$test3 = new Container('flex flex-col gap-2');
|
||||
$test3->addComponent(new \PHPNative\Ui\Widget\Label('Test 3: flex-row gap-8', 'text-lg font-bold'));
|
||||
$row3 = new Container('flex flex-row gap-8 bg-white p-4');
|
||||
$row3->addComponent(new Button('Btn X', 'px-4 py-2 bg-orange-600 text-white rounded'));
|
||||
$row3->addComponent(new Button('Btn Y', 'px-4 py-2 bg-pink-600 text-white rounded'));
|
||||
$test3->addComponent($row3);
|
||||
$mainContainer->addComponent($test3);
|
||||
|
||||
// Test 4: Column with gap-4
|
||||
$test4 = new Container('flex flex-col gap-2');
|
||||
$test4->addComponent(new \PHPNative\Ui\Widget\Label('Test 4: flex-col gap-4', 'text-lg font-bold'));
|
||||
$col = new Container('flex flex-col gap-4 bg-white p-4');
|
||||
$col->addComponent(new Button('Top Button', 'px-4 py-2 bg-teal-600 text-white rounded'));
|
||||
$col->addComponent(new Button('Bottom Button', 'px-4 py-2 bg-indigo-600 text-white rounded'));
|
||||
$test4->addComponent($col);
|
||||
$mainContainer->addComponent($test4);
|
||||
|
||||
$window->setRoot($mainContainer);
|
||||
$app->addWindow($window);
|
||||
|
||||
echo "Gap Test Example\n";
|
||||
echo "You should see:\n";
|
||||
echo "- Row 1: 2 buttons with 8px gap (gap-2)\n";
|
||||
echo "- Row 2: 2 buttons with 16px gap (gap-4)\n";
|
||||
echo "- Row 3: 2 buttons with 32px gap (gap-8)\n";
|
||||
echo "- Column: 2 buttons stacked with 16px gap (gap-4)\n\n";
|
||||
|
||||
$app->run();
|
||||
@ -4,6 +4,7 @@ require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use PHPNative\Framework\Application;
|
||||
use PHPNative\Framework\IconFontRegistry;
|
||||
use PHPNative\Framework\Settings;
|
||||
use PHPNative\Tailwind\Data\Icon as IconName;
|
||||
use PHPNative\Ui\Widget\Button;
|
||||
use PHPNative\Ui\Widget\Container;
|
||||
@ -42,7 +43,10 @@ if ($iconFontPath !== null) {
|
||||
|
||||
$app = new Application();
|
||||
$window = new Window('Windows Application Example', 800, 600);
|
||||
$currentApiKey = '';
|
||||
|
||||
// Initialize settings
|
||||
$settings = new Settings('WindowsAppExample');
|
||||
$currentApiKey = $settings->get('api_key', '');
|
||||
|
||||
/** @var Label|null $statusLabel */
|
||||
$statusLabel = null;
|
||||
@ -53,7 +57,7 @@ $mainContainer = new Container('flex flex-col bg-gray-100');
|
||||
// Modal dialog setup (hidden by default)
|
||||
$apiKeyInput = new TextInput('API Key', 'w-full border border-gray-300 rounded px-3 py-2 bg-white text-black');
|
||||
|
||||
$modalDialog = new Container('bg-white border border-gray-300 rounded p-6 flex flex-col w-96 gap-3 shadow-lg');
|
||||
$modalDialog = new Container('bg-white rounded-lg p-6 flex flex-col w-96 gap-3');
|
||||
$modalDialog->addComponent(new Label('API Einstellungen', 'text-xl font-bold text-black'));
|
||||
$modalDialog->addComponent(new Label(
|
||||
'Bitte gib deinen API Key ein, um externe Dienste zu verbinden.',
|
||||
@ -65,17 +69,16 @@ $fieldContainer->addComponent(new Label('API Key', 'text-sm text-gray-600'));
|
||||
$fieldContainer->addComponent($apiKeyInput);
|
||||
$modalDialog->addComponent($fieldContainer);
|
||||
|
||||
$buttonRow = new Container('flex flex-row justify-end gap-2');
|
||||
$buttonRow = new Container('flex flex-row gap-2 justify-end');
|
||||
$cancelButton = new Button('Abbrechen', 'px-4 py-2 bg-gray-200 text-black rounded hover:bg-gray-300');
|
||||
$saveButton = new Button('Speichern', 'px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center');
|
||||
$saveIcon = new Icon(IconName::save, 18, 'text-white');
|
||||
$saveIcon = new Icon(IconName::save, 18, 'text-white mr-2');
|
||||
$saveButton->setIcon($saveIcon);
|
||||
$buttonRow->addComponent($cancelButton);
|
||||
$buttonRow->addComponent($saveButton);
|
||||
$modalDialog->addComponent($buttonRow);
|
||||
|
||||
$modal = new Modal($modalDialog);
|
||||
$modal->setBackdropAlpha(180);
|
||||
$modal = new Modal($modalDialog, 'flex items-center justify-center bg-black', 200);
|
||||
|
||||
// === 1. MenuBar ===
|
||||
$menuBar = new MenuBar();
|
||||
@ -113,7 +116,7 @@ $tabContainer = new TabContainer('flex-1');
|
||||
|
||||
// Tab 1: Table with data
|
||||
$tab1 = new Container('flex flex-col p-4');
|
||||
$table = new Table(style: 'bg-lime-200');
|
||||
$table = new Table(style: '');
|
||||
|
||||
$table->setColumns([
|
||||
['key' => 'id', 'title' => 'ID', 'width' => 80],
|
||||
@ -186,13 +189,18 @@ $cancelButton->setOnClick(function () use ($menuBar, $modal) {
|
||||
$modal->setVisible(false);
|
||||
});
|
||||
|
||||
$saveButton->setOnClick(function () use (&$currentApiKey, $apiKeyInput, $menuBar, $modal, &$statusLabel) {
|
||||
$saveButton->setOnClick(function () use ($settings, &$currentApiKey, $apiKeyInput, $menuBar, $modal, &$statusLabel) {
|
||||
$currentApiKey = trim($apiKeyInput->getValue());
|
||||
|
||||
// Save to settings
|
||||
$settings->set('api_key', $currentApiKey);
|
||||
$settings->save();
|
||||
|
||||
if ($statusLabel !== null) {
|
||||
$masked = strlen($currentApiKey) > 4
|
||||
? (str_repeat('*', max(0, strlen($currentApiKey) - 4)) . substr($currentApiKey, -4))
|
||||
: $currentApiKey;
|
||||
$statusLabel->setText('API-Key gespeichert: ' . $masked);
|
||||
$statusLabel->setText('API-Key gespeichert: ' . $masked . ' (' . $settings->getPath() . ')');
|
||||
}
|
||||
$menuBar->closeAllMenus();
|
||||
$modal->setVisible(false);
|
||||
|
||||
213
src/Framework/Settings.php
Normal file
213
src/Framework/Settings.php
Normal file
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Framework;
|
||||
|
||||
/**
|
||||
* Cross-platform settings manager
|
||||
* Stores application settings in JSON format in platform-appropriate directories
|
||||
*/
|
||||
class Settings
|
||||
{
|
||||
private string $appName;
|
||||
private string $configPath;
|
||||
private array $data = [];
|
||||
private bool $loaded = false;
|
||||
|
||||
public function __construct(string $appName)
|
||||
{
|
||||
$this->appName = $appName;
|
||||
$this->configPath = $this->getConfigPath();
|
||||
$this->ensureConfigDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platform-specific configuration directory
|
||||
*/
|
||||
private function getConfigPath(): string
|
||||
{
|
||||
$os = PHP_OS_FAMILY;
|
||||
|
||||
switch ($os) {
|
||||
case 'Windows':
|
||||
// Windows: %APPDATA%\AppName\config.json
|
||||
$appData = getenv('APPDATA') ?: (getenv('USERPROFILE') . '\\AppData\\Roaming');
|
||||
return $appData . DIRECTORY_SEPARATOR . $this->appName . DIRECTORY_SEPARATOR . 'config.json';
|
||||
|
||||
case 'Darwin':
|
||||
// macOS: ~/Library/Application Support/AppName/config.json
|
||||
$home = getenv('HOME') ?: posix_getpwuid(posix_getuid())['dir'];
|
||||
return $home . '/Library/Application Support/' . $this->appName . '/config.json';
|
||||
|
||||
case 'Linux':
|
||||
default:
|
||||
// Linux: ~/.config/AppName/config.json (XDG Base Directory)
|
||||
$xdgConfig = getenv('XDG_CONFIG_HOME');
|
||||
if (!$xdgConfig) {
|
||||
$home = getenv('HOME') ?: posix_getpwuid(posix_getuid())['dir'];
|
||||
$xdgConfig = $home . '/.config';
|
||||
}
|
||||
return $xdgConfig . DIRECTORY_SEPARATOR . $this->appName . DIRECTORY_SEPARATOR . 'config.json';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the configuration directory exists
|
||||
*/
|
||||
private function ensureConfigDirectory(): void
|
||||
{
|
||||
$dir = dirname($this->configPath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from disk
|
||||
*/
|
||||
private function load(): void
|
||||
{
|
||||
if ($this->loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists($this->configPath)) {
|
||||
$contents = file_get_contents($this->configPath);
|
||||
$decoded = json_decode($contents, true);
|
||||
if (is_array($decoded)) {
|
||||
$this->data = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save settings to disk
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
$this->ensureConfigDirectory();
|
||||
|
||||
$json = json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
if ($json === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_put_contents($this->configPath, $json, LOCK_EX) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a setting value
|
||||
*/
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
$this->load();
|
||||
|
||||
// Support dot notation for nested keys (e.g., "api.key")
|
||||
$keys = explode('.', $key);
|
||||
$value = $this->data;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
if (!is_array($value) || !array_key_exists($k, $value)) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$k];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a setting value
|
||||
*/
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
$this->load();
|
||||
|
||||
// Support dot notation for nested keys
|
||||
$keys = explode('.', $key);
|
||||
$current = &$this->data;
|
||||
|
||||
foreach ($keys as $i => $k) {
|
||||
if ($i === count($keys) - 1) {
|
||||
// Last key - set the value
|
||||
$current[$k] = $value;
|
||||
} else {
|
||||
// Create nested array if it doesn't exist
|
||||
if (!isset($current[$k]) || !is_array($current[$k])) {
|
||||
$current[$k] = [];
|
||||
}
|
||||
$current = &$current[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a setting exists
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
$this->load();
|
||||
|
||||
$keys = explode('.', $key);
|
||||
$value = $this->data;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
if (!is_array($value) || !array_key_exists($k, $value)) {
|
||||
return false;
|
||||
}
|
||||
$value = $value[$k];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a setting
|
||||
*/
|
||||
public function remove(string $key): void
|
||||
{
|
||||
$this->load();
|
||||
|
||||
$keys = explode('.', $key);
|
||||
$current = &$this->data;
|
||||
|
||||
foreach ($keys as $i => $k) {
|
||||
if ($i === count($keys) - 1) {
|
||||
// Last key - remove it
|
||||
unset($current[$k]);
|
||||
} else {
|
||||
if (!isset($current[$k]) || !is_array($current[$k])) {
|
||||
return;
|
||||
}
|
||||
$current = &$current[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings as an array
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$this->load();
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all settings
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->data = [];
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the config file
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->configPath;
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,15 @@ namespace PHPNative\Tailwind\Data;
|
||||
|
||||
enum Icon:int
|
||||
{
|
||||
case plus = 57669;
|
||||
|
||||
case save = 57697;
|
||||
// FontAwesome 6 Free Solid Unicode code points
|
||||
case plus = 0xf067; // f067 - plus
|
||||
case save = 0xf0c7; // f0c7 - floppy-disk (save)
|
||||
case check = 0xf00c; // f00c - check
|
||||
case times = 0xf00d; // f00d - xmark (close)
|
||||
case edit = 0xf044; // f044 - pen-to-square (edit)
|
||||
case trash = 0xf2ed; // f2ed - trash-can
|
||||
case search = 0xf002; // f002 - magnifying-glass (search)
|
||||
case user = 0xf007; // f007 - user
|
||||
case cog = 0xf013; // f013 - gear (settings)
|
||||
case home = 0xf015; // f015 - house (home)
|
||||
}
|
||||
|
||||
52
src/Tailwind/Parser/Gap.php
Normal file
52
src/Tailwind/Parser/Gap.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPNative\Tailwind\Parser;
|
||||
|
||||
class Gap implements Parser
|
||||
{
|
||||
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Gap
|
||||
{
|
||||
$x = null;
|
||||
$y = null;
|
||||
|
||||
// gap-{n} - both x and y
|
||||
preg_match_all('/\bgap-(\d+)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$value = (int)$output_array[1][0] * 4; // Tailwind uses a 4px scale
|
||||
$x = $value;
|
||||
$y = $value;
|
||||
}
|
||||
|
||||
// gap-x-{n}
|
||||
preg_match_all('/\bgap-x-(\d+)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$x = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
// gap-y-{n}
|
||||
preg_match_all('/\bgap-y-(\d+)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$y = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
if ($x !== null || $y !== null) {
|
||||
$gap = new \PHPNative\Tailwind\Style\Gap($x ?? 0, $y ?? 0);
|
||||
error_log("Gap parsed from '$style': x={$gap->x}, y={$gap->y}");
|
||||
return $gap;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function merge(\PHPNative\Tailwind\Style\Gap $class, \PHPNative\Tailwind\Style\Gap $style)
|
||||
{
|
||||
if ($style->x !== 0) {
|
||||
$class->x = $style->x;
|
||||
}
|
||||
if ($style->y !== 0) {
|
||||
$class->y = $style->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ class Padding implements Parser
|
||||
$t = null;
|
||||
$b = null;
|
||||
|
||||
preg_match_all('/p-(\d*)/', $style, $output_array);
|
||||
preg_match_all('/\bp-(\d*)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
// Tailwind uses a 4px scale
|
||||
$l = (int)$output_array[1][0] * 4;
|
||||
@ -22,34 +22,34 @@ class Padding implements Parser
|
||||
$b = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
preg_match_all('/px-(\d*)/', $style, $output_array);
|
||||
preg_match_all('/\bpx-(\d*)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$l = (int)$output_array[1][0] * 4;
|
||||
$r = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
preg_match_all('/py-(\d*)/', $style, $output_array);
|
||||
preg_match_all('/\bpy-(\d*)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$t = (int)$output_array[1][0] * 4;
|
||||
$b = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
preg_match_all('/pt-(\d*)/', $style, $output_array);
|
||||
preg_match_all('/\bpt-(\d*)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$t = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
preg_match_all('/pb-(\d*)/', $style, $output_array);
|
||||
preg_match_all('/\bpb-(\d*)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$b = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
preg_match_all('/pl-(\d*)/', $style, $output_array);
|
||||
preg_match_all('/\bpl-(\d*)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$l = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
preg_match_all('/pr-(\d*)/', $style, $output_array);
|
||||
preg_match_all('/\bpr-(\d*)\b/', $style, $output_array);
|
||||
if (count($output_array[0]) > 0) {
|
||||
$r = (int)$output_array[1][0] * 4;
|
||||
}
|
||||
|
||||
50
src/Tailwind/Style/Gap.php
Normal file
50
src/Tailwind/Style/Gap.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace PHPNative\Tailwind\Style;
|
||||
|
||||
class Gap implements Style
|
||||
{
|
||||
public int $x;
|
||||
public int $y;
|
||||
|
||||
public function __construct(int $x = 0, int $y = 0)
|
||||
{
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
}
|
||||
|
||||
public static function parse(string $class): null|self
|
||||
{
|
||||
// gap-{n} - both x and y
|
||||
if (preg_match('/^gap-(\d+)$/', $class, $matches)) {
|
||||
$value = (int) $matches[1] * 4; // Tailwind uses 4px units
|
||||
return new self($value, $value);
|
||||
}
|
||||
|
||||
// gap-x-{n}
|
||||
if (preg_match('/^gap-x-(\d+)$/', $class, $matches)) {
|
||||
$value = (int) $matches[1] * 4;
|
||||
return new self($value, 0);
|
||||
}
|
||||
|
||||
// gap-y-{n}
|
||||
if (preg_match('/^gap-y-(\d+)$/', $class, $matches)) {
|
||||
$value = (int) $matches[1] * 4;
|
||||
return new self(0, $value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function merge(Style $other): Style
|
||||
{
|
||||
if (!($other instanceof self)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new self(
|
||||
$other->x !== 0 ? $other->x : $this->x,
|
||||
$other->y !== 0 ? $other->y : $this->y,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -69,6 +69,9 @@ class StyleParser
|
||||
if($m = \PHPNative\Tailwind\Parser\Margin::parse($style)) {
|
||||
return $m;
|
||||
}
|
||||
if($g = \PHPNative\Tailwind\Parser\Gap::parse($style)) {
|
||||
return $g;
|
||||
}
|
||||
if($o = \PHPNative\Tailwind\Parser\Overflow::parse($style)) {
|
||||
return $o;
|
||||
}
|
||||
|
||||
@ -402,7 +402,10 @@ abstract class Component
|
||||
$this->markDirty(false, false);
|
||||
}
|
||||
foreach ($this->children as $child) {
|
||||
$child->handleMouseMove($mouseX, $mouseY);
|
||||
// Skip overlays - they are handled separately by the Window
|
||||
if (!$child->isOverlay()) {
|
||||
$child->handleMouseMove($mouseX, $mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -183,10 +183,34 @@ class Container extends Component
|
||||
$isRow = $flex->direction === DirectionEnum::row;
|
||||
$availableSpace = $isRow ? $this->contentViewport->width : $this->contentViewport->height;
|
||||
|
||||
// Get gap from styles
|
||||
$gap = $this->computedStyles[\PHPNative\Tailwind\Style\Gap::class] ?? null;
|
||||
$gapSize = 0;
|
||||
if ($gap) {
|
||||
$gapSize = $isRow ? $gap->x : $gap->y;
|
||||
// Debug output
|
||||
if ($gapSize > 0) {
|
||||
error_log("Container gap detected: " . $gapSize . "px (" . ($isRow ? "row" : "col") . ")");
|
||||
}
|
||||
}
|
||||
|
||||
// First pass: calculate fixed sizes and count flex-grow items
|
||||
$childSizes = [];
|
||||
$flexGrowCount = 0;
|
||||
$usedSpace = 0;
|
||||
$nonOverlayCount = 0;
|
||||
|
||||
// Count non-overlay children first
|
||||
foreach ($this->children as $child) {
|
||||
if (!$child->isOverlay()) {
|
||||
$nonOverlayCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add gap space to used space (n-1 gaps for n children)
|
||||
if ($nonOverlayCount > 1 && $gapSize > 0) {
|
||||
$usedSpace += ($nonOverlayCount - 1) * $gapSize;
|
||||
}
|
||||
|
||||
foreach ($this->children as $index => $child) {
|
||||
// Skip overlays in flex layout
|
||||
@ -254,6 +278,7 @@ class Container extends Component
|
||||
|
||||
// Second pass: assign sizes and position children
|
||||
$currentPosition = $isRow ? $this->contentViewport->x : $this->contentViewport->y;
|
||||
$childIndex = 0;
|
||||
|
||||
foreach ($this->children as $index => $child) {
|
||||
// Skip overlays in flex layout
|
||||
@ -264,6 +289,11 @@ class Container extends Component
|
||||
$childSize = $childSizes[$index];
|
||||
$size = $childSize['flexGrow'] ? $flexGrowSize : $childSize['size'];
|
||||
|
||||
// Add gap before this child (except for first child)
|
||||
if ($childIndex > 0 && $gapSize > 0) {
|
||||
$currentPosition += $gapSize;
|
||||
}
|
||||
|
||||
// Create viewport for child
|
||||
if ($isRow) {
|
||||
// Flex row
|
||||
@ -291,6 +321,7 @@ class Container extends Component
|
||||
|
||||
$child->setViewport($childViewport);
|
||||
$child->layout($textRenderer);
|
||||
$childIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -131,6 +131,21 @@ class Modal extends Container
|
||||
$this->markDirty(false);
|
||||
}
|
||||
|
||||
public function setVisible(bool $visible): void
|
||||
{
|
||||
$wasVisible = $this->isVisible();
|
||||
parent::setVisible($visible);
|
||||
|
||||
// When modal becomes visible, clear hover states on background components
|
||||
if ($visible && !$wasVisible && $this->attachedWindow) {
|
||||
// Send a mouse move event outside the window to clear all hover states
|
||||
$rootComponent = $this->attachedWindow->getRoot();
|
||||
if ($rootComponent) {
|
||||
$rootComponent->handleMouseMove(-1000, -1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function handleMouseClick(float $mouseX, float $mouseY, int $button): bool
|
||||
{
|
||||
if (!$this->isVisible()) {
|
||||
@ -147,7 +162,10 @@ class Modal extends Container
|
||||
return;
|
||||
}
|
||||
|
||||
// Always handle mouse move in modal to prevent background hover states
|
||||
parent::handleMouseMove($mouseX, $mouseY);
|
||||
|
||||
// Don't propagate to components below the modal
|
||||
}
|
||||
|
||||
public function handleMouseWheel(float $mouseX, float $mouseY, float $deltaY): bool
|
||||
|
||||
@ -222,9 +222,27 @@ class Window
|
||||
$this->mouseX = $event['x'] ?? 0;
|
||||
$this->mouseY = $event['y'] ?? 0;
|
||||
|
||||
// Propagate mouse move to root component
|
||||
// Check overlays first (in reverse z-index order - highest first)
|
||||
if ($this->rootComponent) {
|
||||
$this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
|
||||
$overlays = $this->rootComponent->collectOverlays();
|
||||
usort($overlays, fn($a, $b) => $b->getZIndex() <=> $a->getZIndex());
|
||||
|
||||
$handled = false;
|
||||
foreach ($overlays as $overlay) {
|
||||
if ($overlay->isVisible()) {
|
||||
$overlay->handleMouseMove($this->mouseX, $this->mouseY);
|
||||
$handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If overlay is visible, send fake event to background to clear hover states
|
||||
if ($handled) {
|
||||
$this->rootComponent->handleMouseMove(-1000, -1000);
|
||||
} else {
|
||||
// If no overlay handled it, propagate to normal components
|
||||
$this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user