Framework updates
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -165,8 +165,8 @@ class Dispatcher
|
||||
// For GET requests: redirect to the proper route
|
||||
$params = array_merge($extra_params, $request->query->all());
|
||||
|
||||
// Generate proper URL using Rsx::Route
|
||||
$proper_url = Rsx::Route($controller_name, $action_name, $params);
|
||||
// Generate proper URL using Rsx::Route (signature: "Controller::method", $params)
|
||||
$proper_url = Rsx::Route($controller_name . '::' . $action_name, $params);
|
||||
|
||||
console_debug('DISPATCH', 'Redirecting GET to proper route:', $proper_url);
|
||||
|
||||
@@ -209,8 +209,9 @@ class Dispatcher
|
||||
|
||||
if ($route_pattern) {
|
||||
// Generate proper URL for the SPA action
|
||||
// Note: SPA actions use class name only (action_name is ignored for SPA routes)
|
||||
$params = array_merge($extra_params, $request->query->all());
|
||||
$proper_url = Rsx::Route($controller_name, $action_name, $params);
|
||||
$proper_url = Rsx::Route($controller_name, $params);
|
||||
|
||||
console_debug('DISPATCH', 'Redirecting to SPA action route:', $proper_url);
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*
|
||||
* - Elements are tracked individually (not by selector)
|
||||
* - Calling again with same group name adds more elements to the group
|
||||
* - If element is already in the same group, no-op (idempotent)
|
||||
* - If element is in a different group, it's moved to the new group
|
||||
* - Recalculates immediately and on window resize (debounced 100ms)
|
||||
* - Returns the jQuery object for chaining
|
||||
*
|
||||
@@ -68,8 +70,10 @@
|
||||
* a. Remove min-width from all elements in group (measure natural width)
|
||||
* b. Find max scrollWidth across all connected elements
|
||||
* c. Apply max as min-width to all connected elements
|
||||
* 3. On window resize (debounced 100ms): recalculate all groups
|
||||
* 4. Disconnected elements are pruned on each calculation
|
||||
* 3. Child components are found via shallowFind('.Component') and their
|
||||
* ready events trigger debounced recalculation
|
||||
* 4. On window resize (debounced 100ms): recalculate all groups
|
||||
* 5. Disconnected elements are pruned on each calculation
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -78,8 +82,8 @@ class Width_Group {
|
||||
// Registry of groups: { "group-name": [element, element, ...] }
|
||||
static _groups = {};
|
||||
|
||||
// Debounced resize handler (created on first use)
|
||||
static _resize_handler = null;
|
||||
// Debounced recalculate handler (shared by resize and component ready)
|
||||
static _debounced_recalculate = null;
|
||||
|
||||
// Whether resize listener is attached
|
||||
static _resize_attached = false;
|
||||
@@ -104,17 +108,50 @@ class Width_Group {
|
||||
Width_Group._groups[group_name] = [];
|
||||
}
|
||||
|
||||
// Add each element to the group (avoid duplicates)
|
||||
// Add each element to the group
|
||||
this.each(function () {
|
||||
const element = this;
|
||||
if (!Width_Group._groups[group_name].includes(element)) {
|
||||
Width_Group._groups[group_name].push(element);
|
||||
const current_group = element._width_group;
|
||||
|
||||
// Already in this group - no-op
|
||||
if (current_group === group_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In a different group - remove from old group first
|
||||
if (current_group && Width_Group._groups[current_group]) {
|
||||
const old_group = Width_Group._groups[current_group];
|
||||
const index = old_group.indexOf(element);
|
||||
if (index !== -1) {
|
||||
old_group.splice(index, 1);
|
||||
element.style.minWidth = '';
|
||||
}
|
||||
// Clean up empty group
|
||||
if (old_group.length === 0) {
|
||||
delete Width_Group._groups[current_group];
|
||||
}
|
||||
}
|
||||
|
||||
// Add to new group
|
||||
Width_Group._groups[group_name].push(element);
|
||||
element._width_group = group_name;
|
||||
});
|
||||
|
||||
// Ensure resize listener is attached
|
||||
Width_Group._attach_resize_listener();
|
||||
|
||||
// Listen for child component ready events
|
||||
this.each(function () {
|
||||
$(this).shallowFind('.Component').each(function () {
|
||||
const component = $(this).component();
|
||||
if (component) {
|
||||
component.on('ready', () => {
|
||||
Width_Group._schedule_recalculate();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Calculate immediately
|
||||
Width_Group._calculate_group(group_name);
|
||||
|
||||
@@ -152,11 +189,12 @@ class Width_Group {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear min-width from elements still in DOM
|
||||
// Clear min-width and group tracking from elements
|
||||
for (const element of elements) {
|
||||
if (element.isConnected) {
|
||||
element.style.minWidth = '';
|
||||
}
|
||||
delete element._width_group;
|
||||
}
|
||||
|
||||
// Remove group from registry
|
||||
@@ -166,6 +204,20 @@ class Width_Group {
|
||||
Width_Group._maybe_detach_resize_listener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a debounced recalculation of all groups
|
||||
* Used by resize handler and component ready events
|
||||
* @private
|
||||
*/
|
||||
static _schedule_recalculate() {
|
||||
if (!Width_Group._debounced_recalculate) {
|
||||
Width_Group._debounced_recalculate = debounce(() => {
|
||||
Width_Group._calculate_all();
|
||||
}, 100);
|
||||
}
|
||||
Width_Group._debounced_recalculate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the resize listener if not already attached
|
||||
* @private
|
||||
@@ -175,14 +227,9 @@ class Width_Group {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create debounced handler on first use
|
||||
if (!Width_Group._resize_handler) {
|
||||
Width_Group._resize_handler = debounce(() => {
|
||||
Width_Group._calculate_all();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
$(window).on('resize.width_group', Width_Group._resize_handler);
|
||||
$(window).on('resize.width_group', () => {
|
||||
Width_Group._schedule_recalculate();
|
||||
});
|
||||
Width_Group._resize_attached = true;
|
||||
}
|
||||
|
||||
@@ -218,8 +265,14 @@ class Width_Group {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter to only connected elements
|
||||
elements = elements.filter(el => el.isConnected);
|
||||
// Filter to only connected elements, clean up disconnected ones
|
||||
elements = elements.filter(el => {
|
||||
if (el.isConnected) {
|
||||
return true;
|
||||
}
|
||||
delete el._width_group;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Update registry with pruned list
|
||||
Width_Group._groups[group_name] = elements;
|
||||
|
||||
@@ -27,6 +27,10 @@ API
|
||||
group will have their min-width set to match the widest element.
|
||||
Returns the jQuery object for chaining.
|
||||
|
||||
Each element can only belong to one width group at a time:
|
||||
- If already in the same group: no-op (idempotent)
|
||||
- If in a different group: moved to the new group
|
||||
|
||||
Example:
|
||||
$(".toolbar .btn").width_group("toolbar-buttons");
|
||||
|
||||
@@ -64,14 +68,29 @@ AUTOMATIC CLEANUP
|
||||
Explicit $.width_group_destroy() calls are optional but can be used for
|
||||
immediate cleanup.
|
||||
|
||||
COMPONENT INTEGRATION
|
||||
When width_group() is called, it automatically finds child components
|
||||
within each element using shallowFind('.Component'). For each component
|
||||
found, it listens for the 'ready' event and triggers a debounced
|
||||
recalculation when the component becomes ready.
|
||||
|
||||
This handles cases where a width group contains components that load
|
||||
asynchronously - the widths will be recalculated once the component
|
||||
content is rendered.
|
||||
|
||||
All recalculation triggers (resize, component ready) share the same
|
||||
debounced function (100ms) to prevent excessive recalculations.
|
||||
|
||||
HOW IT WORKS
|
||||
1. On width_group() call, elements are added to a named registry
|
||||
2. Calculation runs:
|
||||
2. Calculation runs immediately:
|
||||
a. Remove min-width from all elements (measure natural width)
|
||||
b. Find max scrollWidth across all connected elements
|
||||
c. Apply max as min-width to all connected elements
|
||||
3. On window resize (debounced 100ms), recalculate all groups
|
||||
4. Disconnected elements are pruned on each calculation
|
||||
3. Child components found via shallowFind('.Component') have their
|
||||
ready events listened to for debounced recalculation
|
||||
4. On window resize (debounced 100ms), recalculate all groups
|
||||
5. Disconnected elements are pruned on each calculation
|
||||
|
||||
EXAMPLES
|
||||
Button group with consistent widths:
|
||||
|
||||
111
app/RSpade/upstream_changes/scss_loop_refactor_12_31.txt
Executable file
111
app/RSpade/upstream_changes/scss_loop_refactor_12_31.txt
Executable file
@@ -0,0 +1,111 @@
|
||||
SCSS Loop Refactoring for Responsive Styles
|
||||
============================================
|
||||
Date: 2024-12-31
|
||||
Affects: rsx/theme/responsive.scss (and any project-specific responsive SCSS)
|
||||
|
||||
SUMMARY
|
||||
-------
|
||||
The framework's responsive.scss has been refactored to use SCSS loops (@each)
|
||||
for repetitive class generation. Projects with custom responsive SCSS should
|
||||
analyze their files for similar optimization opportunities.
|
||||
|
||||
CHANGES IN FRAMEWORK
|
||||
--------------------
|
||||
1. col-5ths responsive classes now generated via @each loop
|
||||
2. CSS custom properties (--bp-*) now generated via @each loop
|
||||
3. $breakpoints-up map added for consistency in -up mixins
|
||||
4. Reduced code from ~55 lines to ~30 lines for col-5ths section
|
||||
|
||||
ANALYSIS GUIDE FOR PROJECT SCSS
|
||||
-------------------------------
|
||||
Review your responsive SCSS files for these patterns:
|
||||
|
||||
PATTERN 1: Repeated classes with only breakpoint name changing
|
||||
--------------------------------------------------------------
|
||||
Before:
|
||||
.my-thing-mobile { @include mobile { ... } }
|
||||
.my-thing-tablet { @include tablet-up { ... } }
|
||||
.my-thing-desktop { @include desktop { ... } }
|
||||
|
||||
After:
|
||||
$my-thing-breakpoints: (
|
||||
'mobile': 'max' $bp-desktop,
|
||||
'tablet': 'min' $bp-tablet,
|
||||
'desktop': 'min' $bp-desktop
|
||||
);
|
||||
|
||||
@each $name, $config in $my-thing-breakpoints {
|
||||
$type: nth($config, 1);
|
||||
$value: nth($config, 2);
|
||||
|
||||
.my-thing-#{$name} {
|
||||
@if $type == 'max' {
|
||||
@media (max-width: #{$value - 0.02px}) { ... }
|
||||
} @else {
|
||||
@media (min-width: $value) { ... }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PATTERN 2: CSS custom properties for each breakpoint
|
||||
----------------------------------------------------
|
||||
Before:
|
||||
:root {
|
||||
--my-bp-tablet: #{$bp-tablet};
|
||||
--my-bp-desktop: #{$bp-desktop};
|
||||
--my-bp-desktop-lg: #{$bp-desktop-lg};
|
||||
}
|
||||
|
||||
After:
|
||||
$my-breakpoints: (
|
||||
'tablet': $bp-tablet,
|
||||
'desktop': $bp-desktop,
|
||||
'desktop-lg': $bp-desktop-lg
|
||||
);
|
||||
|
||||
:root {
|
||||
@each $name, $value in $my-breakpoints {
|
||||
--my-bp-#{$name}: #{$value};
|
||||
}
|
||||
}
|
||||
|
||||
PATTERN 3: Shared styles in a mixin
|
||||
-----------------------------------
|
||||
If multiple classes share identical styles, extract to a mixin:
|
||||
|
||||
@mixin my-shared-styles {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.base-class { @include my-shared-styles; }
|
||||
|
||||
@each $name, $config in $breakpoints {
|
||||
.responsive-#{$name} {
|
||||
@media (...) { @include my-shared-styles; }
|
||||
}
|
||||
}
|
||||
|
||||
WHEN NOT TO USE LOOPS
|
||||
---------------------
|
||||
- When classes have different internal logic (not just breakpoint differences)
|
||||
- When the repetition is 2-3 items (loop overhead not worth it)
|
||||
- When the pattern would obscure the code's intent
|
||||
|
||||
LIMITATION: DYNAMIC MIXIN CALLS
|
||||
-------------------------------
|
||||
SCSS does not support: @include #{$variable}
|
||||
|
||||
This means you cannot dynamically call mixins by name. The workaround is to
|
||||
define breakpoint values in a map and generate media queries directly, as
|
||||
shown in Pattern 1 above.
|
||||
|
||||
ACTION REQUIRED
|
||||
---------------
|
||||
1. Review project-specific responsive SCSS files
|
||||
2. Identify repeated patterns (3+ similar blocks)
|
||||
3. Refactor using @each loops where appropriate
|
||||
4. Verify compiled CSS output matches previous version
|
||||
|
||||
NO BREAKING CHANGES - this is a code organization improvement only.
|
||||
Reference in New Issue
Block a user