@jsenv/navi 0.10.2 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +13838 -23291
- package/dist/jsenv_navi.js.map +1281 -0
- package/package.json +6 -8
- package/index.js +0 -122
- package/src/action_private_properties.js +0 -11
- package/src/action_proxy_test.html +0 -353
- package/src/action_run_states.js +0 -5
- package/src/actions.js +0 -1401
- package/src/browser_integration/browser_integration.js +0 -216
- package/src/browser_integration/document_back_and_forward.js +0 -17
- package/src/browser_integration/document_loading_signal.js +0 -100
- package/src/browser_integration/document_state_signal.js +0 -9
- package/src/browser_integration/document_url_signal.js +0 -9
- package/src/browser_integration/use_is_visited.js +0 -19
- package/src/browser_integration/via_history.js +0 -232
- package/src/browser_integration/via_navigation.js +0 -168
- package/src/components/action_execution/form_context.js +0 -5
- package/src/components/action_execution/render_actionable_component.jsx +0 -29
- package/src/components/action_execution/use_action.js +0 -99
- package/src/components/action_execution/use_execute_action.js +0 -193
- package/src/components/action_execution/use_run_on_mount.js +0 -9
- package/src/components/action_renderer.jsx +0 -125
- package/src/components/callout/callout.js +0 -990
- package/src/components/callout/callout_demo.html +0 -201
- package/src/components/callout/test_dynamic_positioning.html +0 -161
- package/src/components/callout/test_html_document_iframe.html +0 -182
- package/src/components/demos/0_button_demo.html +0 -707
- package/src/components/demos/10_column_reordering_debug.html +0 -277
- package/src/components/demos/11_table_selection_debug.html +0 -432
- package/src/components/demos/1_checkbox_demo.html +0 -754
- package/src/components/demos/2_input_textual_demo.html +0 -286
- package/src/components/demos/3_radio_demo.html +0 -874
- package/src/components/demos/4_select_demo.html +0 -100
- package/src/components/demos/5_list_scrollable_demo.html +0 -153
- package/src/components/demos/6_tablist_demo.html +0 -77
- package/src/components/demos/7_table_selection_demo.html +0 -176
- package/src/components/demos/8_table_fixed_headers_demo.html +0 -584
- package/src/components/demos/9_table_column_drag_demo.html +0 -325
- package/src/components/demos/action/0_button_demo.html +0 -204
- package/src/components/demos/action/10_shortcuts_demo.html +0 -189
- package/src/components/demos/action/11_nested_shortcuts_demo.xhtml +0 -401
- package/src/components/demos/action/1_input_text_demo.html +0 -876
- package/src/components/demos/action/2_form_multiple.html +0 -303
- package/src/components/demos/action/3_details_demo.html +0 -203
- package/src/components/demos/action/4_input_checkbox_demo.html +0 -731
- package/src/components/demos/action/5_input_checkbox_state_demo.html +0 -270
- package/src/components/demos/action/6_checkbox_list_demo.html +0 -341
- package/src/components/demos/action/7_radio_list_demo.html +0 -357
- package/src/components/demos/action/8_editable_demo.html +0 -431
- package/src/components/demos/action/9_link_demo.html +0 -194
- package/src/components/demos/demo.md +0 -0
- package/src/components/demos/route/basic/basic.html +0 -14
- package/src/components/demos/route/basic/basic_route_demo.jsx +0 -224
- package/src/components/demos/route/multi/multi.html +0 -14
- package/src/components/demos/route/multi/multi_route_demo.jsx +0 -277
- package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +0 -695
- package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +0 -429
- package/src/components/demos/ui_transition/2_height_transition_test.html +0 -295
- package/src/components/details/details.jsx +0 -245
- package/src/components/details/summary_marker.jsx +0 -141
- package/src/components/edition/editable.jsx +0 -186
- package/src/components/error_boundary_context.js +0 -9
- package/src/components/field/README.md +0 -247
- package/src/components/field/button.jsx +0 -429
- package/src/components/field/checkbox_list.jsx +0 -185
- package/src/components/field/collect_form_element_values.js +0 -82
- package/src/components/field/custom_field.js +0 -106
- package/src/components/field/form.jsx +0 -209
- package/src/components/field/input.jsx +0 -16
- package/src/components/field/input_checkbox.jsx +0 -434
- package/src/components/field/input_radio.jsx +0 -432
- package/src/components/field/input_textual.jsx +0 -389
- package/src/components/field/label.jsx +0 -46
- package/src/components/field/radio_list.jsx +0 -183
- package/src/components/field/select.jsx +0 -256
- package/src/components/field/use_action_events.js +0 -132
- package/src/components/field/use_form_events.js +0 -59
- package/src/components/field/use_ui_state_controller.js +0 -506
- package/src/components/item_tracker/README.md +0 -461
- package/src/components/item_tracker/use_isolated_item_tracker.jsx +0 -209
- package/src/components/item_tracker/use_isolated_item_tracker_demo.html +0 -148
- package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +0 -460
- package/src/components/item_tracker/use_item_tracker.jsx +0 -143
- package/src/components/item_tracker/use_item_tracker_demo.html +0 -207
- package/src/components/item_tracker/use_item_tracker_demo.jsx +0 -216
- package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +0 -87
- package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +0 -61
- package/src/components/keyboard_shortcuts/keyboard_key_meta.js +0 -17
- package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +0 -371
- package/src/components/keyboard_shortcuts/os.js +0 -9
- package/src/components/layout/demos/demo_flex.html +0 -638
- package/src/components/layout/demos/demo_layout_style_buttons.html +0 -351
- package/src/components/layout/demos/demo_layout_style_input.html +0 -226
- package/src/components/layout/demos/demo_layout_style_text.html +0 -514
- package/src/components/layout/flex.jsx +0 -109
- package/src/components/layout/layout_context.jsx +0 -3
- package/src/components/layout/spacing.jsx +0 -20
- package/src/components/layout/use_layout_style.js +0 -249
- package/src/components/link/link.jsx +0 -267
- package/src/components/link/link_with_icon.jsx +0 -52
- package/src/components/loader/loader_background.jsx +0 -372
- package/src/components/loader/loading_spinner.jsx +0 -68
- package/src/components/loader/network_speed.js +0 -83
- package/src/components/loader/rectangle_loading.jsx +0 -244
- package/src/components/props_composition/demos/demo_with_props_style.html +0 -81
- package/src/components/props_composition/with_props_class_name.js +0 -37
- package/src/components/props_composition/with_props_style.js +0 -26
- package/src/components/route.jsx +0 -19
- package/src/components/selection/selection.jsx +0 -1583
- package/src/components/svg/font_sized_svg.jsx +0 -59
- package/src/components/svg/icon_and_text.jsx +0 -21
- package/src/components/svg/svg_mask_overlay.jsx +0 -105
- package/src/components/table/drag/table_drag.jsx +0 -506
- package/src/components/table/resize/table_resize.jsx +0 -650
- package/src/components/table/resize/table_size.js +0 -43
- package/src/components/table/selection/table_selection.js +0 -106
- package/src/components/table/selection/table_selection.jsx +0 -203
- package/src/components/table/sticky/sticky_group.js +0 -354
- package/src/components/table/sticky/table_sticky.js +0 -25
- package/src/components/table/sticky/table_sticky.jsx +0 -501
- package/src/components/table/table.jsx +0 -721
- package/src/components/table/table_css.js +0 -211
- package/src/components/table/table_ui.jsx +0 -49
- package/src/components/table/use_cells_and_columns.js +0 -90
- package/src/components/table/use_object_array_to_cells.js +0 -46
- package/src/components/table/z_indexes.js +0 -23
- package/src/components/tablist/tablist.jsx +0 -99
- package/src/components/text/demos/demo_text_and_icon.html +0 -421
- package/src/components/text/overflow.jsx +0 -15
- package/src/components/text/text.jsx +0 -83
- package/src/components/text/text_and_count.jsx +0 -28
- package/src/components/ui_transition.jsx +0 -128
- package/src/components/use_auto_focus.js +0 -94
- package/src/components/use_batch_during_render.js +0 -33
- package/src/components/use_debounce_true.js +0 -31
- package/src/components/use_dependencies_diff.js +0 -35
- package/src/components/use_focus_group.js +0 -20
- package/src/components/use_initial_value.js +0 -78
- package/src/components/use_is_visited.js +0 -19
- package/src/components/use_ref_array.js +0 -38
- package/src/components/use_signal_sync.js +0 -50
- package/src/components/use_stable_callback.js +0 -68
- package/src/components/use_state_array.js +0 -47
- package/src/docs/actions.md +0 -250
- package/src/docs/demos/resource/action_status.jsx +0 -42
- package/src/docs/demos/resource/demo.md +0 -1
- package/src/docs/demos/resource/resource_demo_0.html +0 -84
- package/src/docs/demos/resource/resource_demo_10_post_gc.html +0 -364
- package/src/docs/demos/resource/resource_demo_11_describe_many.html +0 -362
- package/src/docs/demos/resource/resource_demo_2.html +0 -173
- package/src/docs/demos/resource/resource_demo_3_filtered_users.html +0 -415
- package/src/docs/demos/resource/resource_demo_4_details.html +0 -284
- package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +0 -115
- package/src/docs/demos/resource/resource_demo_6_gc.html +0 -217
- package/src/docs/demos/resource/resource_demo_7_child_gc.html +0 -240
- package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +0 -319
- package/src/docs/demos/resource/resource_demo_9_describe_one.html +0 -472
- package/src/docs/demos/resource/tata.jsx +0 -3
- package/src/docs/demos/resource/toto.jsx +0 -3
- package/src/docs/demos/user_nav/user_nav.html +0 -12
- package/src/docs/demos/user_nav/user_nav.jsx +0 -330
- package/src/docs/resource_dependencies.md +0 -103
- package/src/docs/resource_with_params.md +0 -80
- package/src/navi_css_vars.js +0 -14
- package/src/notes.md +0 -34
- package/src/route/route.js +0 -596
- package/src/route/route.xtest.html +0 -228
- package/src/store/array_signal_store.js +0 -537
- package/src/store/local_storage_signal.js +0 -17
- package/src/store/resource_graph.js +0 -1304
- package/src/store/tests/resource_graph_autoreload_demo.html +0 -12
- package/src/store/tests/resource_graph_autoreload_demo.jsx +0 -964
- package/src/store/tests/resource_graph_dependencies.test_manual.js +0 -95
- package/src/store/value_in_local_storage.js +0 -187
- package/src/symbol_object_signal.js +0 -1
- package/src/use_action_data.js +0 -10
- package/src/use_action_status.js +0 -47
- package/src/utils/add_many_event_listeners.js +0 -15
- package/src/utils/array_add_remove.js +0 -61
- package/src/utils/array_signal.js +0 -15
- package/src/utils/compare_two_js_values.js +0 -172
- package/src/utils/execute_with_cleanup.js +0 -21
- package/src/utils/get_caller_info.js +0 -85
- package/src/utils/is_signal.js +0 -20
- package/src/utils/js_value_weak_map.js +0 -162
- package/src/utils/js_value_weak_map_demo.html +0 -690
- package/src/utils/merge_two_js_values.js +0 -53
- package/src/utils/stringify_for_display.js +0 -131
- package/src/utils/weak_effect.js +0 -48
- package/src/validation/constraints/confirm_constraint.js +0 -14
- package/src/validation/constraints/create_unique_value_constraint.js +0 -27
- package/src/validation/constraints/native_constraints.js +0 -338
- package/src/validation/constraints/readonly_constraint.js +0 -41
- package/src/validation/constraints/same_as_constraint.js +0 -42
- package/src/validation/constraints/single_space_constraint.js +0 -13
- package/src/validation/custom_constraint_validation.js +0 -793
- package/src/validation/custom_message.js +0 -18
- package/src/validation/demos/browser_style.png +0 -0
- package/src/validation/demos/demo_same_as_constraint.html +0 -259
- package/src/validation/demos/form_validation_demo.html +0 -142
- package/src/validation/demos/form_validation_demo_preact.html +0 -87
- package/src/validation/demos/form_validation_native_popover_demo.html +0 -168
- package/src/validation/demos/form_validation_vs_native_demo.html +0 -172
- package/src/validation/hooks/use_constraints.js +0 -23
- package/src/validation/hooks/use_custom_validation_ref.js +0 -73
- package/src/validation/hooks/use_validation_message.js +0 -19
- package/src/validation/input_change_effect.js +0 -106
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FontSizedSvg component
|
|
3
|
-
*
|
|
4
|
-
* This component wraps an SVG element to make it inherit the current font size.
|
|
5
|
-
* It creates a container that's exactly 1em × 1em in size, allowing the SVG to scale
|
|
6
|
-
* proportionally with the surrounding text.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* ```jsx
|
|
10
|
-
* <FontSizedSvg>
|
|
11
|
-
* <svg width="100%" height="100%" viewBox="...">
|
|
12
|
-
* <path d="..." />
|
|
13
|
-
* </svg>
|
|
14
|
-
* </FontSizedSvg>
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* Notes:
|
|
18
|
-
* - The wrapped SVG should use width="100%" and height="100%" to fill the container
|
|
19
|
-
* - This ensures SVG icons match the current text size without additional styling
|
|
20
|
-
* - Useful for inline icons that should respect the parent's font-size
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { withPropsStyle } from "../props_composition/with_props_style.js";
|
|
24
|
-
|
|
25
|
-
import.meta.css = /* css */ `
|
|
26
|
-
.navi_font_sized_svg {
|
|
27
|
-
display: flex;
|
|
28
|
-
width: 1em;
|
|
29
|
-
height: 1em;
|
|
30
|
-
flex-shrink: 0;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-self: center;
|
|
33
|
-
line-height: 1em;
|
|
34
|
-
}
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
export const FontSizedSvg = ({
|
|
38
|
-
width = "1em",
|
|
39
|
-
height = "1em",
|
|
40
|
-
style,
|
|
41
|
-
children,
|
|
42
|
-
...props
|
|
43
|
-
}) => {
|
|
44
|
-
return (
|
|
45
|
-
<span
|
|
46
|
-
{...props}
|
|
47
|
-
className="navi_font_sized_svg"
|
|
48
|
-
style={withPropsStyle(
|
|
49
|
-
{
|
|
50
|
-
width: width === "1em" ? undefined : width,
|
|
51
|
-
height: height === "1em" ? undefined : height,
|
|
52
|
-
},
|
|
53
|
-
style,
|
|
54
|
-
)}
|
|
55
|
-
>
|
|
56
|
-
{children}
|
|
57
|
-
</span>
|
|
58
|
-
);
|
|
59
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { FontSizedSvg } from "./font_sized_svg.jsx";
|
|
2
|
-
|
|
3
|
-
export const IconAndText = ({ icon, children, ...rest }) => {
|
|
4
|
-
if (typeof icon === "function") icon = icon({});
|
|
5
|
-
|
|
6
|
-
return (
|
|
7
|
-
<span
|
|
8
|
-
className="icon_and_text"
|
|
9
|
-
{...rest}
|
|
10
|
-
style={{
|
|
11
|
-
display: "flex",
|
|
12
|
-
alignItems: "center",
|
|
13
|
-
gap: "0.1em",
|
|
14
|
-
...rest.style,
|
|
15
|
-
}}
|
|
16
|
-
>
|
|
17
|
-
<FontSizedSvg className="icon">{icon}</FontSizedSvg>
|
|
18
|
-
<span className="text">{children}</span>
|
|
19
|
-
</span>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SVGComposition Component
|
|
3
|
-
*
|
|
4
|
-
* Creates composite SVGs by combining independent SVG elements with masking.
|
|
5
|
-
*
|
|
6
|
-
* This component solves the challenge of combining independently created SVGs into
|
|
7
|
-
* a single visual composition. Each SVG can have its own coordinate system, viewBox,
|
|
8
|
-
* and styling, allowing for maximum reusability of individual icons or graphics.
|
|
9
|
-
*
|
|
10
|
-
* When overlaying SVGs, each subsequent overlay "cuts out" its portion from the base SVG,
|
|
11
|
-
* creating a seamless integration where SVGs appear to interact with each other visually.
|
|
12
|
-
*
|
|
13
|
-
* Key benefits:
|
|
14
|
-
* - Maintains each SVG's independence - use them individually elsewhere
|
|
15
|
-
* - Handles different viewBox dimensions automatically
|
|
16
|
-
* - Works with any SVG components regardless of internal implementation
|
|
17
|
-
* - Supports unlimited overlay elements
|
|
18
|
-
* - Creates proper masking between elements for visual integration
|
|
19
|
-
*
|
|
20
|
-
* Usage example combining two independent icon components:
|
|
21
|
-
* ```jsx
|
|
22
|
-
* <SVGMaskOverlay viewBox="0 0 24 24">
|
|
23
|
-
* <DatabaseSvg />
|
|
24
|
-
* <svg x="12" y="12" width="16" height="16" overflow="visible">
|
|
25
|
-
* <PlusSvg />
|
|
26
|
-
* </svg>
|
|
27
|
-
* </SVGMaskOverlay>
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* @param {Object} props - Component properties
|
|
31
|
-
* @param {string} props.viewBox - The main viewBox for the composition (required)
|
|
32
|
-
* @param {ReactNode[]} props.children - SVG elements (first is base, rest are overlays)
|
|
33
|
-
* @returns {ReactElement} A composed SVG with all elements properly masked
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
import { cloneElement } from "preact";
|
|
37
|
-
|
|
38
|
-
import.meta.css = /* css */ `
|
|
39
|
-
.svg_mask_content * {
|
|
40
|
-
fill: black !important;
|
|
41
|
-
stroke: black !important;
|
|
42
|
-
fill-opacity: 1 !important;
|
|
43
|
-
stroke-opacity: 1 !important;
|
|
44
|
-
color: black !important;
|
|
45
|
-
opacity: 1 !important;
|
|
46
|
-
}
|
|
47
|
-
`;
|
|
48
|
-
|
|
49
|
-
export const SVGMaskOverlay = ({ viewBox, children }) => {
|
|
50
|
-
if (!Array.isArray(children)) {
|
|
51
|
-
return children;
|
|
52
|
-
}
|
|
53
|
-
if (children.length === 1) {
|
|
54
|
-
return children[0];
|
|
55
|
-
}
|
|
56
|
-
if (!viewBox) {
|
|
57
|
-
console.error("SVGComposition requires an explicit viewBox");
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// First SVG is the base, all others are overlays
|
|
62
|
-
const [baseSvg, ...overlaySvgs] = children;
|
|
63
|
-
|
|
64
|
-
// Generate unique ID for this instance
|
|
65
|
-
const instanceId = `svgmo-${Math.random().toString(36).slice(2, 9)}`;
|
|
66
|
-
|
|
67
|
-
// Create nested masked elements
|
|
68
|
-
let maskedElement = baseSvg;
|
|
69
|
-
|
|
70
|
-
// Apply each mask in sequence
|
|
71
|
-
overlaySvgs.forEach((overlaySvg, index) => {
|
|
72
|
-
const maskId = `mask-${instanceId}-${index}`;
|
|
73
|
-
maskedElement = <g mask={`url(#${maskId})`}>{maskedElement}</g>;
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<svg viewBox={viewBox} width="100%" height="100%">
|
|
78
|
-
<defs>
|
|
79
|
-
{/* Define masks that respect position */}
|
|
80
|
-
{overlaySvgs.map((overlaySvg, index) => {
|
|
81
|
-
const maskId = `mask-${instanceId}-${index}`;
|
|
82
|
-
|
|
83
|
-
// IMPORTANT: clone the overlay SVG exactly as is, just add the mask class
|
|
84
|
-
return (
|
|
85
|
-
<mask id={maskId} key={maskId}>
|
|
86
|
-
{/* White background makes everything visible by default */}
|
|
87
|
-
<rect width="100%" height="100%" fill="white" />
|
|
88
|
-
|
|
89
|
-
{/* EXACT CLONE of the overlay SVG */}
|
|
90
|
-
{cloneElement(overlaySvg, {
|
|
91
|
-
className: "svg_mask_content", // Apply styling to make it black
|
|
92
|
-
})}
|
|
93
|
-
</mask>
|
|
94
|
-
);
|
|
95
|
-
})}
|
|
96
|
-
</defs>
|
|
97
|
-
|
|
98
|
-
{/* Base SVG with all masks applied */}
|
|
99
|
-
{maskedElement}
|
|
100
|
-
|
|
101
|
-
{/* Render all overlays */}
|
|
102
|
-
{overlaySvgs}
|
|
103
|
-
</svg>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createDragToMoveGestureController,
|
|
3
|
-
createPubSub,
|
|
4
|
-
dragAfterThreshold,
|
|
5
|
-
getDropTargetInfo,
|
|
6
|
-
getScrollContainer,
|
|
7
|
-
stickyAsRelativeCoords,
|
|
8
|
-
} from "@jsenv/dom";
|
|
9
|
-
import { createContext } from "preact";
|
|
10
|
-
import { forwardRef } from "preact/compat";
|
|
11
|
-
import { useMemo, useState } from "preact/hooks";
|
|
12
|
-
|
|
13
|
-
import { useStableCallback } from "../../use_stable_callback.js";
|
|
14
|
-
import { Z_INDEX_CELL_FOREGROUND, Z_INDEX_DROP_PREVIEW } from "../z_indexes.js";
|
|
15
|
-
|
|
16
|
-
const DEBUG_VISUAL = false;
|
|
17
|
-
|
|
18
|
-
import.meta.css = /* css */ `
|
|
19
|
-
.navi_table_drag_clone_container {
|
|
20
|
-
position: absolute;
|
|
21
|
-
left: var(--table-visual-left);
|
|
22
|
-
top: var(--table-visual-top);
|
|
23
|
-
width: var(--table-visual-width);
|
|
24
|
-
height: var(--table-visual-height);
|
|
25
|
-
/* background: rgba(0, 0, 0, 0.5); */
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.navi_table_cell[data-grabbed]::before,
|
|
29
|
-
.navi_table_cell[data-grabbed]::after {
|
|
30
|
-
box-shadow: none !important;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/* We preprend ".navi_table_container" to ensure it propertly overrides */
|
|
34
|
-
.navi_table_drag_clone_container .navi_table_cell {
|
|
35
|
-
opacity: ${DEBUG_VISUAL ? 0.5 : 0};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.navi_table_drag_clone_container .navi_table_cell[data-grabbed] {
|
|
39
|
-
opacity: 0.7;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.navi_table_drag_clone_container .navi_table_cell_sticky_frontier {
|
|
43
|
-
opacity: 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.navi_table_drag_clone_container .navi_table_cell[data-sticky-left],
|
|
47
|
-
.navi_table_drag_clone_container .navi_table_cell[data-sticky-top] {
|
|
48
|
-
position: relative;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.navi_table_cell_foreground {
|
|
52
|
-
pointer-events: none;
|
|
53
|
-
position: absolute;
|
|
54
|
-
inset: 0;
|
|
55
|
-
background: lightgrey;
|
|
56
|
-
opacity: 0;
|
|
57
|
-
z-index: ${Z_INDEX_CELL_FOREGROUND};
|
|
58
|
-
}
|
|
59
|
-
.navi_table_cell[data-first-row] .navi_table_cell_foreground {
|
|
60
|
-
background-color: grey;
|
|
61
|
-
}
|
|
62
|
-
.navi_table_cell_foreground[data-visible] {
|
|
63
|
-
opacity: 1;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.navi_table_drag_clone_container .navi_table_cell_foreground {
|
|
67
|
-
opacity: 1;
|
|
68
|
-
background-color: rgba(255, 255, 255, 0.2);
|
|
69
|
-
backdrop-filter: blur(10px);
|
|
70
|
-
}
|
|
71
|
-
.navi_table_drag_clone_container
|
|
72
|
-
.navi_table_cell[data-first-row][data-grabbed] {
|
|
73
|
-
opacity: 1;
|
|
74
|
-
}
|
|
75
|
-
.navi_table_drag_clone_container
|
|
76
|
-
.navi_table_cell[data-first-row]
|
|
77
|
-
.navi_table_cell_foreground {
|
|
78
|
-
opacity: 0;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.navi_table_column_drop_preview {
|
|
82
|
-
position: absolute;
|
|
83
|
-
left: var(--column-left);
|
|
84
|
-
top: var(--column-top);
|
|
85
|
-
width: var(--column-width);
|
|
86
|
-
height: var(--column-height);
|
|
87
|
-
pointer-events: none;
|
|
88
|
-
z-index: ${Z_INDEX_DROP_PREVIEW};
|
|
89
|
-
/* Invisible container - just for positioning */
|
|
90
|
-
background: transparent;
|
|
91
|
-
border: none;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.navi_table_column_drop_preview_line {
|
|
95
|
-
position: absolute;
|
|
96
|
-
top: 0;
|
|
97
|
-
bottom: 0;
|
|
98
|
-
width: 4px;
|
|
99
|
-
background: rgba(0, 0, 255, 0.5);
|
|
100
|
-
opacity: 0;
|
|
101
|
-
left: 0; /* Default: left edge for dropping before */
|
|
102
|
-
transform: translateX(-50%);
|
|
103
|
-
}
|
|
104
|
-
.navi_table_column_drop_preview[data-after]
|
|
105
|
-
.navi_table_column_drop_preview_line {
|
|
106
|
-
left: 100%; /* Right edge for dropping after */
|
|
107
|
-
}
|
|
108
|
-
.navi_table_column_drop_preview[data-visible]
|
|
109
|
-
.navi_table_column_drop_preview_line {
|
|
110
|
-
opacity: 1;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.navi_table_column_drop_preview .arrow_positioner {
|
|
114
|
-
position: absolute;
|
|
115
|
-
left: 0; /* Default: left edge for dropping before */
|
|
116
|
-
display: flex;
|
|
117
|
-
opacity: 0;
|
|
118
|
-
transform: translateX(-50%);
|
|
119
|
-
color: rgba(0, 0, 255, 0.5);
|
|
120
|
-
}
|
|
121
|
-
.navi_table_column_drop_preview[data-after] .arrow_positioner {
|
|
122
|
-
left: 100%; /* Right edge for dropping after */
|
|
123
|
-
}
|
|
124
|
-
.navi_table_column_drop_preview[data-visible] .arrow_positioner {
|
|
125
|
-
opacity: 1;
|
|
126
|
-
}
|
|
127
|
-
.navi_table_column_drop_preview .arrow_positioner[data-top] {
|
|
128
|
-
top: -10px;
|
|
129
|
-
}
|
|
130
|
-
.navi_table_column_drop_preview .arrow_positioner[data-bottom] {
|
|
131
|
-
bottom: -10px;
|
|
132
|
-
}
|
|
133
|
-
.arrow_positioner svg {
|
|
134
|
-
width: 10px;
|
|
135
|
-
height: 10px;
|
|
136
|
-
}
|
|
137
|
-
`;
|
|
138
|
-
|
|
139
|
-
export const TableDragContext = createContext();
|
|
140
|
-
export const useTableDragContextValue = ({
|
|
141
|
-
tableDragCloneContainerRef,
|
|
142
|
-
tableColumnDropPreviewRef,
|
|
143
|
-
columns,
|
|
144
|
-
setColumnOrder,
|
|
145
|
-
canChangeColumnOrder,
|
|
146
|
-
}) => {
|
|
147
|
-
setColumnOrder = useStableCallback(setColumnOrder);
|
|
148
|
-
|
|
149
|
-
const [grabTarget, setGrabTarget] = useState(null);
|
|
150
|
-
const grabColumn = (columnIndex) => {
|
|
151
|
-
setGrabTarget(`column:${columnIndex}`);
|
|
152
|
-
};
|
|
153
|
-
const releaseColumn = (columnIndex, newColumnIndex) => {
|
|
154
|
-
setGrabTarget(null);
|
|
155
|
-
if (columnIndex === newColumnIndex) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const columnIds = columns.map((col) => col.id);
|
|
159
|
-
const columnIdsWithNewOrder = moveItem(
|
|
160
|
-
columnIds,
|
|
161
|
-
columnIndex,
|
|
162
|
-
newColumnIndex,
|
|
163
|
-
);
|
|
164
|
-
setColumnOrder(columnIdsWithNewOrder);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
return useMemo(() => {
|
|
168
|
-
return {
|
|
169
|
-
tableDragCloneContainerRef,
|
|
170
|
-
tableColumnDropPreviewRef,
|
|
171
|
-
grabTarget,
|
|
172
|
-
grabColumn,
|
|
173
|
-
releaseColumn,
|
|
174
|
-
setColumnOrder,
|
|
175
|
-
canChangeColumnOrder,
|
|
176
|
-
};
|
|
177
|
-
}, [grabTarget, canChangeColumnOrder]);
|
|
178
|
-
};
|
|
179
|
-
const moveItem = (array, indexA, indexB) => {
|
|
180
|
-
const newArray = [];
|
|
181
|
-
const movedItem = array[indexA];
|
|
182
|
-
const movingRight = indexA < indexB;
|
|
183
|
-
|
|
184
|
-
for (let i = 0; i < array.length; i++) {
|
|
185
|
-
if (movingRight) {
|
|
186
|
-
// Moving right: add target first, then moved item after
|
|
187
|
-
if (i !== indexA) {
|
|
188
|
-
newArray.push(array[i]);
|
|
189
|
-
}
|
|
190
|
-
if (i === indexB) {
|
|
191
|
-
newArray.push(movedItem);
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
// Moving left: add moved item first, then target after
|
|
195
|
-
if (i === indexB) {
|
|
196
|
-
newArray.push(movedItem);
|
|
197
|
-
}
|
|
198
|
-
if (i !== indexA) {
|
|
199
|
-
newArray.push(array[i]);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return newArray;
|
|
204
|
-
};
|
|
205
|
-
export const swapItem = (array, indexA, indexB) => {
|
|
206
|
-
const newArray = [];
|
|
207
|
-
const itemAtPositionA = array[indexA];
|
|
208
|
-
const itemAtPositionB = array[indexB];
|
|
209
|
-
for (let i = 0; i < array.length; i++) {
|
|
210
|
-
if (i === indexB) {
|
|
211
|
-
// At the new position, put the dragged column
|
|
212
|
-
newArray.push(itemAtPositionA);
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
if (i === indexA) {
|
|
216
|
-
// At the old position, put what was at the new position
|
|
217
|
-
newArray.push(itemAtPositionB);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
// Everything else stays the same
|
|
221
|
-
newArray.push(array[i]);
|
|
222
|
-
}
|
|
223
|
-
return newArray;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
export const TableDragCloneContainer = forwardRef((props, ref) => {
|
|
227
|
-
const { tableId } = props;
|
|
228
|
-
|
|
229
|
-
return (
|
|
230
|
-
<div
|
|
231
|
-
ref={ref}
|
|
232
|
-
className="navi_table_drag_clone_container"
|
|
233
|
-
data-overlay-for={tableId}
|
|
234
|
-
></div>
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
export const TableColumnDropPreview = forwardRef((props, ref) => {
|
|
238
|
-
return (
|
|
239
|
-
<div ref={ref} className="navi_table_column_drop_preview">
|
|
240
|
-
<div className="arrow_positioner" data-top="">
|
|
241
|
-
{/* This is an arrow pointing down */}
|
|
242
|
-
<svg fill="currentColor" viewBox="0 0 30.727 30.727">
|
|
243
|
-
<path
|
|
244
|
-
d="M29.994,10.183L15.363,24.812L0.733,10.184c-0.977-0.978-0.977-2.561,0-3.536c0.977-0.977,2.559-0.976,3.536,0
|
|
245
|
-
l11.095,11.093L26.461,6.647c0.977-0.976,2.559-0.976,3.535,0C30.971,7.624,30.971,9.206,29.994,10.183z"
|
|
246
|
-
/>
|
|
247
|
-
</svg>
|
|
248
|
-
</div>
|
|
249
|
-
<div className="navi_table_column_drop_preview_line"></div>
|
|
250
|
-
<div className="arrow_positioner" data-bottom="">
|
|
251
|
-
{/* This is an arrow pointing up */}
|
|
252
|
-
<svg fill="currentColor" viewBox="0 0 30.727 30.727">
|
|
253
|
-
<path
|
|
254
|
-
d="M29.994,20.544L15.363,5.915L0.733,20.543c-0.977,0.978-0.977,2.561,0,3.536c0.977,0.977,2.559,0.976,3.536,0
|
|
255
|
-
l11.095-11.093L26.461,24.08c0.977,0.976,2.559,0.976,3.535,0C30.971,23.103,30.971,21.521,29.994,20.544z"
|
|
256
|
-
/>
|
|
257
|
-
</svg>
|
|
258
|
-
</div>
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
export const initDragTableColumnViaPointer = (
|
|
264
|
-
pointerdownEvent,
|
|
265
|
-
{ tableDragCloneContainer, dropPreview, onGrab, onDrag, onRelease },
|
|
266
|
-
) => {
|
|
267
|
-
dragAfterThreshold(pointerdownEvent, () => {
|
|
268
|
-
const [teardown, addTeardown] = createPubSub();
|
|
269
|
-
|
|
270
|
-
const tableCell = pointerdownEvent.target.closest(".navi_table_cell");
|
|
271
|
-
const table = tableCell.closest(".navi_table");
|
|
272
|
-
const columnIndex = Array.from(tableCell.parentNode.children).indexOf(
|
|
273
|
-
tableCell,
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
// Track the drop target column index (starts as current column)
|
|
277
|
-
let dropColumnIndex = columnIndex;
|
|
278
|
-
|
|
279
|
-
const tableClone = table.cloneNode(true);
|
|
280
|
-
// ensure [data-drag-obstacle] inside the table clone are ignored
|
|
281
|
-
tableClone.setAttribute("data-drag-ignore", "");
|
|
282
|
-
|
|
283
|
-
// Scale down the table clone and set transform origin to mouse grab point
|
|
284
|
-
// const tableRect = table.getBoundingClientRect();
|
|
285
|
-
// const mouseX = mousedownEvent.clientX - tableRect.left;
|
|
286
|
-
// const mouseY = mousedownEvent.clientY - tableRect.top;
|
|
287
|
-
// tableClone.style.transform = "scale(1.2)";
|
|
288
|
-
// tableClone.style.transformOrigin = `${mouseX}px ${mouseY}px`;
|
|
289
|
-
|
|
290
|
-
update_sticky_elements: {
|
|
291
|
-
// In the table clone we need to convert sticky elements to position: relative
|
|
292
|
-
// with calculated offsets that match their appearance in the original context
|
|
293
|
-
const scrollContainer = getScrollContainer(table);
|
|
294
|
-
|
|
295
|
-
// important: only on cells, not on <col> nor <tr>
|
|
296
|
-
const originalStickyCells = table.querySelectorAll(
|
|
297
|
-
".navi_table_cell[data-sticky-left], .navi_table_cell[data-sticky-top]",
|
|
298
|
-
);
|
|
299
|
-
const cloneStickyCells = tableClone.querySelectorAll(
|
|
300
|
-
".navi_table_cell[data-sticky-left], .navi_table_cell[data-sticky-top]",
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
originalStickyCells.forEach((originalCell, index) => {
|
|
304
|
-
const cloneCell = cloneStickyCells[index];
|
|
305
|
-
const relativePosition = stickyAsRelativeCoords(
|
|
306
|
-
originalCell,
|
|
307
|
-
// Our clone is absolutely positioned on top of <table />
|
|
308
|
-
// So we need the sticky position relative to <table />
|
|
309
|
-
table,
|
|
310
|
-
{
|
|
311
|
-
scrollContainer,
|
|
312
|
-
},
|
|
313
|
-
);
|
|
314
|
-
if (relativePosition) {
|
|
315
|
-
const [relativeLeft, relativeTop] = relativePosition;
|
|
316
|
-
cloneCell.style.position = "relative";
|
|
317
|
-
if (relativeLeft !== undefined) {
|
|
318
|
-
cloneCell.style.left = `${relativeLeft}px`;
|
|
319
|
-
}
|
|
320
|
-
if (relativeTop !== undefined) {
|
|
321
|
-
cloneCell.style.top = `${relativeTop}px`;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
sync_data_grabbed: {
|
|
328
|
-
// ensure [data-grabbed] are present in the table clone
|
|
329
|
-
// we could retry on "sync_attributes" but we want to be sure it's done asap to prevent table from being displayed at all
|
|
330
|
-
// I fear without this we might have an intermediate step where the table column clone is not visible
|
|
331
|
-
// as [data-grabbed] are not set
|
|
332
|
-
// Would not be a problem but this ensure we see exactly the table clone right away preventing any possibility
|
|
333
|
-
// of visual glitches
|
|
334
|
-
const tableCloneCells = tableClone.querySelectorAll(".navi_table_cell");
|
|
335
|
-
tableCloneCells.forEach((cellClone) => {
|
|
336
|
-
const cellColumnIndex = Array.from(
|
|
337
|
-
cellClone.parentNode.children,
|
|
338
|
-
).indexOf(cellClone);
|
|
339
|
-
if (cellColumnIndex === columnIndex) {
|
|
340
|
-
cellClone.setAttribute("data-grabbed", "");
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
append_in_dom: {
|
|
346
|
-
tableDragCloneContainer.appendChild(tableClone);
|
|
347
|
-
addTeardown(() => {
|
|
348
|
-
tableClone.remove();
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
sync_attributes: {
|
|
353
|
-
// Sync attribute changes from original table to clone
|
|
354
|
-
// This is used to:
|
|
355
|
-
// - handle table cells being selected as result of mousedown on the <th />
|
|
356
|
-
// - nothing else is supposed to change in the original <table /> during the drag gesture
|
|
357
|
-
const syncTableAttributes = createTableAttributeSync(table, tableClone);
|
|
358
|
-
addTeardown(() => {
|
|
359
|
-
syncTableAttributes.disconnect();
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const colgroup = table.querySelector(".navi_colgroup");
|
|
364
|
-
const colElements = Array.from(colgroup.children);
|
|
365
|
-
|
|
366
|
-
const col = colElements[columnIndex];
|
|
367
|
-
const colgroupClone = tableClone.querySelector(".navi_colgroup");
|
|
368
|
-
const colClone = colgroupClone.children[columnIndex];
|
|
369
|
-
const dragToMoveGestureController = createDragToMoveGestureController({
|
|
370
|
-
name: "move-column",
|
|
371
|
-
direction: { x: true },
|
|
372
|
-
threshold: 0,
|
|
373
|
-
onGrab,
|
|
374
|
-
onDrag: (gestureInfo) => {
|
|
375
|
-
onDrag?.(gestureInfo, dropColumnIndex);
|
|
376
|
-
},
|
|
377
|
-
resetPositionAfterRelease: !DEBUG_VISUAL,
|
|
378
|
-
onRelease: (gestureInfo) => {
|
|
379
|
-
if (!DEBUG_VISUAL) {
|
|
380
|
-
teardown();
|
|
381
|
-
}
|
|
382
|
-
onRelease?.(gestureInfo, dropColumnIndex);
|
|
383
|
-
},
|
|
384
|
-
});
|
|
385
|
-
const dragToMoveGesture = dragToMoveGestureController.grabViaPointer(
|
|
386
|
-
pointerdownEvent,
|
|
387
|
-
{
|
|
388
|
-
element: colClone,
|
|
389
|
-
referenceElement: col,
|
|
390
|
-
elementToMove: tableClone,
|
|
391
|
-
},
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
drop_preview: {
|
|
395
|
-
// Get all column elements for drop target detection
|
|
396
|
-
const dropCandidateElements = colElements.filter(
|
|
397
|
-
(col) =>
|
|
398
|
-
!(col.getAttribute("data-drag-obstacle") || "").includes(
|
|
399
|
-
"move-column",
|
|
400
|
-
),
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
const updateDropTarget = (dropTargetInfo) => {
|
|
404
|
-
const targetColumn = dropTargetInfo.element;
|
|
405
|
-
const targetColumnIndex = colElements.indexOf(targetColumn);
|
|
406
|
-
dropColumnIndex = targetColumnIndex;
|
|
407
|
-
if (dropColumnIndex === columnIndex) {
|
|
408
|
-
dropPreview.removeAttribute("data-visible");
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
// Position the invisible container to match the target column
|
|
412
|
-
const { left, top, width, height } =
|
|
413
|
-
targetColumn.getBoundingClientRect();
|
|
414
|
-
dropPreview.style.setProperty("--column-left", `${left}px`);
|
|
415
|
-
dropPreview.style.setProperty("--column-top", `${top}px`);
|
|
416
|
-
dropPreview.style.setProperty("--column-width", `${width}px`);
|
|
417
|
-
dropPreview.style.setProperty("--column-height", `${height}px`);
|
|
418
|
-
// Set data-after attribute to control line position via CSS
|
|
419
|
-
if (dropColumnIndex > columnIndex) {
|
|
420
|
-
// Dropping after: CSS will position line at right edge (100%)
|
|
421
|
-
dropPreview.setAttribute("data-after", "");
|
|
422
|
-
} else {
|
|
423
|
-
// Dropping before: CSS will position line at left edge (0%)
|
|
424
|
-
dropPreview.removeAttribute("data-after");
|
|
425
|
-
}
|
|
426
|
-
dropPreview.setAttribute("data-drop-column-index", dropColumnIndex);
|
|
427
|
-
dropPreview.setAttribute("data-visible", "");
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
dragToMoveGesture.addDragCallback((gestureInfo) => {
|
|
431
|
-
const dropTargetInfo = getDropTargetInfo(
|
|
432
|
-
gestureInfo,
|
|
433
|
-
dropCandidateElements,
|
|
434
|
-
);
|
|
435
|
-
if (!dropTargetInfo) {
|
|
436
|
-
dropPreview.removeAttribute("data-visible");
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
updateDropTarget(dropTargetInfo);
|
|
440
|
-
});
|
|
441
|
-
dragToMoveGesture.addReleaseCallback(() => {
|
|
442
|
-
dropPreview.removeAttribute("data-visible");
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return dragToMoveGesture;
|
|
447
|
-
});
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Creates a MutationObserver that syncs attribute changes from original table to clone
|
|
452
|
-
* @param {HTMLElement} table - The original table element
|
|
453
|
-
* @param {HTMLElement} cloneTable - The cloned table element
|
|
454
|
-
* @returns {MutationObserver} The observer instance with disconnect method
|
|
455
|
-
*/
|
|
456
|
-
const createTableAttributeSync = (table, tableClone) => {
|
|
457
|
-
// Create a map to quickly find corresponding elements in the clone
|
|
458
|
-
const createElementMap = () => {
|
|
459
|
-
const map = new Map();
|
|
460
|
-
const cells = table.querySelectorAll(".navi_table_cell");
|
|
461
|
-
const cellClones = tableClone.querySelectorAll(".navi_table_cell");
|
|
462
|
-
for (let i = 0; i < cells.length; i++) {
|
|
463
|
-
if (cellClones[i]) {
|
|
464
|
-
map.set(cells[i], cellClones[i]);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return map;
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const elementMap = createElementMap();
|
|
471
|
-
const observer = new MutationObserver((mutations) => {
|
|
472
|
-
mutations.forEach((mutation) => {
|
|
473
|
-
if (mutation.type === "attributes") {
|
|
474
|
-
const originalElement = mutation.target;
|
|
475
|
-
const cloneElement = elementMap.get(originalElement);
|
|
476
|
-
|
|
477
|
-
if (cloneElement) {
|
|
478
|
-
const attributeName = mutation.attributeName;
|
|
479
|
-
if (attributeName === "style") {
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Sync the attribute change to the clone
|
|
484
|
-
if (originalElement.hasAttribute(attributeName)) {
|
|
485
|
-
const attributeValue = originalElement.getAttribute(attributeName);
|
|
486
|
-
cloneElement.setAttribute(attributeName, attributeValue);
|
|
487
|
-
} else {
|
|
488
|
-
cloneElement.removeAttribute(attributeName);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// Observe attribute changes on all table cells
|
|
496
|
-
const cellsToObserve = table.querySelectorAll(".navi_table_cell");
|
|
497
|
-
cellsToObserve.forEach((cell) => {
|
|
498
|
-
observer.observe(cell, {
|
|
499
|
-
attributes: true,
|
|
500
|
-
attributeOldValue: false,
|
|
501
|
-
subtree: false,
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
return observer;
|
|
506
|
-
};
|