PageSourceSearch

https://www.artres.com/Include/Matrix5/OnDemand/DamView/SidePanelC…?v=MjAyNi0wOS0zMCAwODowNzo1Ny4wMDA

js artres.com collected 2026-10-02 10:26:17 UTC 32,796 bytes, 783 lines download raw bytes

1if (typeof window.SidePanelController === "undefined")
2{
3    (function ()
4    {
5
6        /**
7         * @typedef SidePanelMode
8         * 
9         * @type {"free"|"pinned"}
10        */
11
12        /**
13         * @typedef SidePanelContext
14         * 
15         * @type {Object}
16         * 
17         * @property {HTMLElement} panel The `Panel` element.
18         * @property {HTMLElement} openTrigger The `OpenTrigger` element.
19         * @property {HTMLElement} closeTrigger The `CloseTrigger` element.
20         * @property {HTMLElement} sizer The `Sizer` element.
21         * @property {{free: HTMLElement, pinned: HTMLElement}} changeModeTriggers The elements which cause the Panel to change its mode (pinned/free-floating).
22        */
23
24        /**
25          * @typedef ActivateOptions
26          * 
27          * @type {Object}
28          * 
29          * @property {string} panelSelector CSS selector of the `Panel` element.
30          * @property {string} openTriggerSelector CSS selector of the `OpenTrigger` element.
31          * @property {string} closeTriggerSelector CSS selector of the `CloseTrigger` element.
32          * @property {string} sizerSelector CSS selector of the `Sizer` element.
33          * @property {{free: string, pinned: string}} changeModeTriggerSelectors CSS selectors for the `ChangeModeTrigger_Pinned` element and the `ChangeModeTrigger_Free` element.
34          * @property {SidePanelMode} mode The initial mode of the side panel. 
35          * @property {string} prefID ID to store side panel state as user preference. 
36          * @property {SidePanelMode} [forcedMode] The forced mode of the side panel. If provided, user will not be able to switch between modes.
37          * 
38          * @property {Function} onModeChange Callback when mode changes.
39          * @property {number} autoCollapseThreshold Automatically collapse side panels when viewport width is smaller than this threshold (px).
40          */
41
42        /**
43         * Link: https://link.orangelogic.com/Tasks/22TYZT
44         * This object contains logic that help controlling the folding/expanding of a single DamView side panel.
45         * 
46         * ## Prerequisites: 
47         * For each side panel [Left/Right] in `DamView_VForm`, there must be a set of the following elements:
48         * - `Panel` - the main container of the side panel. Must be a foldable panel. 
49         * - `OpenTrigger` - the element that will cause the Panel to expand. 
50         * - `CloseTrigger` - the element that will cause the Panel to fold. 
51         * - `Sizer` - the element that will be used to calculate the width of the Panel. Its original width must be equal to `Panel`. 
52         * - The elements that will cause the Panel to change its mode (pinned/free-floating):
53         *  + `ChangeModeTrigger_Pinned` - the element that will cause the Panel to change its mode to pinned.
54         *  + `ChangeModeTrigger_Free` - the element that will cause the Panel to change its mode to free-floating.
55         *  
56         * ## Behavior:
57         * The behavior of the side panel will be of the following: 
58         * - Common behavior (regardless of whether the side panel is pinned or not): 
59         *  + Clicking on `CloseTrigger`  will fold `Panel`. 
60         * - When side panel is **not pinned** (i.e. free-floating mode): 
61         *  + `ChangeModeTrigger_Free` must be hidden
62         *  + Hovering on the `OpenTrigger` will expand `Panel`. 
63         *  + Moving cursor out of `Panel` will fold `Panel`. 
64         *  + `Sizer`'s min width must be overridden with 1 (as min width 0 is invali
64d + 1px is enough for our case) so that `Panel` will not take up space from the content) 
65         *  + Clicking on the `ChangeModeTrigger_Pinned` will change the mode to pinned.
66         * - When side panel is **pinned** (i.e. pinned mode):
67         *  + `ChangeModeTrigger_Pinned` must be hidden
68         *  + Clicking on the `OpenTrigger` will expand `Panel`. 
69         *  + Remove 0 overrides from the `Sizer`'s min width so that `Panel` will take up space from the content.
70         *  + Clicking on the `ChangeModeTrigger_Free` will change the mode to free-floating and automatically close `Panel`.
71         * 
72         * @param {SidePanelContext} context
73         * @param {ActivateOptions} options
74         * 
75         * @class
76         */
77        function SidePanelController(context, options)
78        {
79            /** @type {string} */
80            this.id = context.panel.GetId();
81
82            /** @type {SidePanelContext} */
83            this.context = context;
84
85            /** @type {SidePanelMode} */
86            this.mode = options.forcedMode ? options.forcedMode : options.mode;
87
88            /** @type {string} */
89            this.prefID = options.prefID;
90
91            /** @type {?SidePanelMode} */
92            this.forcedMode = options.forcedMode;
93
94            /** @type {?Function} */
95            this.onModeChange = options.onModeChange;
96
97            /** @type {number} */
98            this.autoCollapseThreshold = options.autoCollapseThreshold;
99
100            /** @type {ActivateOptions} */
101            this.options = options;
102
103            /** @type {Function[]} */
104            this._cleanupFunctions = [];
105        }
106
107        /**
108         * Store instances of {@link SidePanelController}
109         * 
110         * @type {Object.<string, SidePanelController>}
111         */
112        SidePanelController.Instances = {};
113
114        /**
115         * @typedef SidePanelControllerConst
116         * 
117         * @property {"free"} SIDE_PANEL_MODE_FREE Side panel mode "free"
118         * @property {"pinned"} SIDE_PANEL_MODE_PINNED Side panel mode "pinned"
119         * @property {SidePanelMode[]} SIDE_PANEL_ALL_MODES All side panel modes
120         * 
121         * @property {boolean} DEBUG_MODE Flag to activate debug logging
122         */
123
124        /**
125         * Constant values
126         * 
127         * @type {SidePanelControllerConst}
128         * 
129         */
130        SidePanelController.Const =
131        {
132            SIDE_PANEL_MODE_FREE: "free",
133            SIDE_PANEL_MODE_PINNED: "pinned",
134            SIDE_PANEL_ALL_MODES: ["free", "pinned"],
135
136            ATTR_DATA_SIDE_PANEL_MODE: "data-side-panel-mode",
137
138            DEBUG_MODE: false,
139        };
140
141        /**
142         * Log an error message to console for debugging purpose
143         */
144        SidePanelController._error = function ()
145        {
146            if (!SidePanelController.Const.DEBUG_MODE) return;
147
148            console.error("%c[DEBUG MODE] SidePanelController%c%s",
149                "color:white; background-color:var(--cx-color-danger-800); padding:8px;",
150                "color:white; background-color:var(--cx-color-primary); padding:8px;",
151                new Date().toLocaleString(undefined, {
152                    year: 'numeric',
153                    month: 'numeric',
154                    day: 'numeric',
155                    hour: 'numeric',
156                    minute: 'numeric',
157                    second: 'numeric',
158                    fractionalSecondDigits: 3
159                }), ...arguments);
160        };
161
162        /**
163         * Log a message to console for debugging purpose
164         * 
165         * @returns
166         */
167        SidePanelController._debug = function ()
168        {
169            if (!SidePanelController.Const.DEBUG_MODE) return;
170
171            console.log("%c[DEBUG MODE] SidePanelController%c%s",
172                "color:white; background-color:var(--cx-color-danger-800); padding:8px;",
173                "color:white; background-color:var(--cx-color-primary); padding:8px;",
174                new Date().toLocaleString(undefined, {
175                    year: 'numeric',
176                    month: 'numeric',
177                    day: 'numeric',
178                    hour: 'numeric',
179                    minute: 'numeric',
180                    second: 'numeric',
181                    fractionalSecondDigits: 3
182                }), ...arguments);
183        };
184
185        /**
186         * Ensure a condition is true. Log an error if it is not.
187         * 
188         * @param {boolean} mustTrueCondition The condition that should be true.
189         * @param {any} messageIfFail Message to log if the condition fails.
190         * 
191         * @returns {boolean} True if the condition is true. False if it fails.
192         */
193        SidePanelController._ensure = function (mustTrueCondition, messageIfFail)
194        {
195            if (!mustTrueCondition)
196            {
197                SidePanelController._error(messageIfFail);
198                return false;
199            }
200            return true;
201        };
202
203        /**
204         * Validate Activate's options
205         * 
206         * @param {ActivateOptions} options Options.
207         * 
208         * @returns {boolean} True if the options are valid.
209         */
210        SidePanelController._validateActivateOptions = function (options)
211        {
212            return SidePanelController._ensure(Utils.IsStringFilled(options.panelSelector), "Panel Selector must be filled")
213                && SidePanelController._ensure(Utils.IsStringFilled(options.openTriggerSelector), "OpenTrigger Selector must be filled")
214                && SidePanelController._ensure(Utils.IsStringFilled(options.closeTriggerSelector), "CloseTrigger Selector must be filled")
215                && SidePanelController._ensure(Utils.IsStringFilled(options.sizerSelector), "Sizer Selector must be filled")
216                && SidePanelController._ensure(options.changeModeTriggerSelectors != null, "ChangeModeTrigger Selectors must be provided")
217                && SidePanelController._ensure(Utils.IsStringFilled(options.changeModeTriggerSelectors.free), "ChangeModeTrigger_Free Selector must be filled")
218                && SidePanelController._ensure(Utils.IsStringFilled(options.changeModeTriggerSelectors.pinned), "ChangeModeTrigger_Pinned Selector must be filled")
219                && SidePanelController._ensure(SidePanelController.Const.SIDE_PANEL_ALL_MODES.includes(options.mode),
220                    'Mode must be any of ["' + SidePanelController.Const.SIDE_PANEL_ALL_MODES.join('","') + '"]. Received: ' + options.mode)
221                && SidePanelController._ensure(Utils.IsStringFilled(options.prefID), "Preference ID must be filled")
222                && SidePanelController._ensure(!Utils.IsStringFilled(options.forcedMode) || SidePanelController.Const.SIDE_PANEL_ALL_MODES.includes(options.forcedMode),
223                    'Forced mode must not be set or must be any of ["' + SidePanelController.Const.SIDE_PANEL_ALL_MODES.join('","') + '"]. Received: ' + options.forcedMode);
224        };
225
226        /**
227         * Refresh controller instance
228         * @param {any} panelID
229         */
230        SidePanelController.RefreshInstance = function (panelID)
231        {
232            var controller = SidePanelController.Instances[panelID];
233            if (!controller) return;
234
235            var options = controller.options;
236            options.mode = controller.mode;
237            controller.Dispose();
238            SidePanelController.Activate(options);
239        };
240
241        /**
242         * Activate the controller on a single side panel.
243         * 
244         * @param {ActivateOptions} options Options.
245         * 
246         */
247        SidePanelController.Activate = function (options)
248        {
249            SidePanelController._debug("Creating new controller with the following options:", options);
250
251            // Validate options
252            if (!SidePanelController._validateActivateOptions(options))
253            {
254                SidePanelController._debug("Receive invalid options", options, "--> No controller created");
255                return;
256            }
257            var panel = document.querySelector(options.panelSelector);
258            var openTrigger = document.querySelector(options.openTriggerSelector);
259            var closeTrigger = document.querySelector(options.closeTriggerSelector);
260            var sizer = document.querySelector(options.sizerSelector);
261            var changeModeTriggers = {
262                free: document.querySelector(options.changeModeTriggerSelectors.free),
263                pinned: document.querySelector(options.changeModeTriggerSelectors.pinned),
264            }
265            if (!panel || !openTrigger || !closeTrigger || !sizer || !changeModeTriggers.free || !changeModeTriggers.pinned)
266            {
267                SidePanelController._debug("Cannot find required elements", {
268                    [options.panelSelector]: panel,
269                    [options.openTriggerSelector]: openTrigger,
270                    [options.closeTriggerSelector]: closeTrigger,
271                    [options.sizerSelector]: sizer,
272                    [options.changeModeTriggerSelectors.free]: changeModeTriggers.free,
273                    [options.changeModeTriggerSelectors.pinned]: changeModeTriggers.pinned,
274                }, "--> No controller created");
275                return;
276            }
277
278            // Create controller
279            var panelID = panel.GetId();
280            var controller = new SidePanelController({
281                panel: panel,
282                openTrigger: openTrigger,
283                closeTrigger: closeTrigger,
284                sizer: sizer,
285                changeModeTriggers,
286            }, options);
287
288            if (SidePanelController.Instances[panelID])
289            {
290                SidePanelController.Instances[panelID].Dispose();
291            }
292            SidePanelController.Instances[panelID] = controller;
293
294            SidePanelController._debug("Created new controller for Side Panel", panelID);
295
296            // Initialize controller
297            controller.Initialize();
298
299            // Register for event to clean up when the panel is removed from the page
300            Matrix3.RegisterForCleanup(panelID, "SidePanelController_" + panelID, function ()
301            {
302                try
303                {
304                    controller.Dispose();
305                    SidePanelController._debug("Disposed the controller for Side Panel", panelID);
306                } catch (e) { }
307            });
308
309            return controller;
310        };
311
312        /**
313         * Log a message to console for debugging purpose
314         */
315        SidePanelController.prototype._debug = function ()
316        {
317            SidePanelController._debug(this.id, ...arguments);
318        };
319
320        /**
321         * Log an error to console for debugging purpose
322         */
323        SidePanelController.prototype._error = function ()
324        {
325            SidePanelController._error(this.id, ...arguments);
326        };
327
328        /**
329         * Ensure a condition is true. Log an error if it is not.
330         * 
331         * @param {boolean} mustTrueCondition The condition that should be true.
332         * @param {any} messageIfFail Message to log if the condition fails.
333         * 
334         * @returns {boolean} True if the condition is true. False if it fails.
335         */
336        SidePanelController.prototype._ensure = function (mustTrue
336Condition, messageIfFail)
337        {
338            if (!mustTrueCondition)
339            {
340                this._error(messageIfFail);
341                return false;
342            }
343            return true;
344        };
345
346        /**
347         * Initialize the controller
348         */
349        SidePanelController.prototype.Initialize = function ()
350        {
351            var controller = this;
352
353            controller._debug("Initializing controller");
354
355            controller._debug("Setup event listeners");
356
357            controller._setupEvents();
358
359            controller._debug("Setup elements for initial mode", controller.mode);
360
361            controller._setupElements(controller.mode);
362
363            controller._debug("Initialized controller");
364        };
365
366        /**
367         * Dispose the controller
368         */
369        SidePanelController.prototype.Dispose = function ()
370        {
371            for (var i = 0; i < this._cleanupFunctions.length; i++)
372            {
373                this._cleanupFunctions[i].call(null);
374            }
375            this._cleanupFunctions = [];
376
377            this.context.sizer.SetProperty(ConstProperties.Const.MAX_RESIZE_WIDTH, this.context.sizer.__originalMaxResizeWidth);
378
379            delete SidePanelController.Instances[this.id];
380        }
381
382        /**
383         * Setup keyboard navigation for Content panel
384         * @private
385         */
386        SidePanelController.prototype._setupKeyboardNavigation = function ()
387        {
388            var controller = this;
389
390            // Find Content panel and OptionsPnl
391            if (!controller.context || !controller.context.panel) {
392                return;
393            }
394
395            var contentPanel = controller.context.panel.querySelector('[id$=":Content"]');
396            var optionsPnl = controller.context.panel.querySelector('[id$=":OptionsPnl"]');
397
398            if (!contentPanel)
399            {
400                return;
401            }
402
403            // Ensure Content panel has tabindex for keyboard focus
404            if (!contentPanel.hasAttribute('tabindex'))
405            {
406                contentPanel.setAttribute('tabindex', '0');
407            }
408
409            // Keyboard event handler for Content panel
410            var onKeyDownContentPanel = function (e)
411            {
412                // Only handle keyboard events when the Content panel itself is focused
413                // Not when focus is on child elements inside the Content panel
414                if (!document.activeElement || document.activeElement !== contentPanel)
415                {
416                    return;
417                }
418
419                var key = e.key;
420
421                // Handle Tab key - move to OptionsPnl
422                if (key === "Tab" && !e.shiftKey && optionsPnl)
423                {
424                    var firstFocusableInOptions = Utils.FindFirstFocusableElement(optionsPnl);
425                    if (firstFocusableInOptions)
426                    {
427                        e.preventDefault();
428                        e.stopPropagation();
429                        firstFocusableInOptions.focus();
430                    }
431                }
432                // Handle Enter key - move focus inside Content panel
433                else if (key === "Enter")
434                {
435                    var firstFocusableInContent = Utils.FindFirstFocusableElement(contentPanel);
436                    if (firstFocusableInContent)
437                    {
438                        e.preventDefault();
439                        e.stopPropagation();
440                        firstFocusableInContent.focus();
441                    }
442                }
443            };
444
445            contentPanel.addEventListener('keydown', onKeyDownContentPanel);
446            controller._cleanupFunctions.push(function () {
447                contentPanel.removeEventListener('keydown', onKeyDownContentPanel);
448                contentPanel = null;
449            });
450
451            // Setup keyboard handler for OptionsPnl to handle Shift+Tab back to Content panel
452            if (optionsPnl)
453            {
454                var onKeyDownOptionsPnl = function (e)
455                {
456                    var key = e.key;
457
458                    // Handle Shift+Tab from first focusable element in OptionsPnl
459                    if (key === "Tab" && e.shiftKey)
460                    {
461                        var activeElement = document.activeElement;
462                        var allFocusable = Utils.FindAllFocusableElements(optionsPnl);
463                        var focusableElements = [];
464
465                        // Filter out the container itself
466                        for (var i = 0; i < allFocusable.length; i++)
467                        {
468                            if (allFocusable[i] !== optionsPnl)
469                            {
470                                focusableElements.push(allFocusable[i]);
471                            }
472                        }
473
474                        // Check if we're on the first focusable element
475                        if (focusableElements.length > 0 && activeElement === focusableElements[0])
476                        {
477                            e.preventDefault();
478                            e.stopPropagation();
479                            contentPanel.focus();
480                        }
481                    }
482                };
483
484                optionsPnl.addEventListener('keydown', onKeyDownOptionsPnl, true);
485                controller._cleanupFunctions.push(function ()
486                {
487                    optionsPnl.removeEventListener('keydown', onKeyDownOptionsPnl, true);
488                    optionsPnl = null;
489                });
490            }
491        };
492
493        /**
494         * Setup event listeners
495         */
496        SidePanelController.prototype._setupEvents = function ()
497        {
498            var controller = this;
499
500            var panelControl = $Control(controller.context.panel.GetId());
501
502            // Setup keyboard navigation for Content panel
503            controller._setupKeyboardNavigation();
504
505            // #region Common behavior
506
507            // Collapse when clicking `CloseTrigger`
508            var onClickCloseTrigger = function ()
509            {
510                panelControl.Action_CloseFold();
511            };
512            controller.context.closeTrigger.addEventListener('click', onClickCloseTrigger);
513            controller._cleanupFunctions.push(function () { controller.context.closeTrigger.removeEventListener('click', onClickCloseTrigger); });
514
515            // Change mode to pinned when clicking `ChangeModeTrigger_Pinned`
516            var onClickPinnedTrigger = function ()
517            {
518                controller.ChangeMode("pinned");
519            };
520            controller.context.changeModeTriggers.pinned.addEventListener('click', onClickPinnedTrigger);
521            controller._cleanupFunctions.push(function () { controller.context.changeModeTriggers.pinned.removeEventListener('click', onClickPinnedTrigger); });
522
523            // Change mode to pinned when clicking `ChangeModeTrigger_Free`
524            var onClickFreeTrigger = function ()
525            {
526                controller.ChangeMode("free");
527            };
528            controller.context.changeModeTriggers.free.addEventListener('click', onClickFreeTrigger);
529            controller._cleanupFunctions.push(function () { controller.context.changeModeTriggers.free.removeEventListener('click', onClickFreeTrigger); });
530
531            // Auto-collapse under screen size threshold
532            var handleResize = function ()
533            {
534                var viewportWidth = Utils.GetViewportWidth();
535                if (viewportWidth < controller.autoCollapseThreshold)
536                {
537                    panelControl.Action_CloseFold();
538                }
539            };
540            window.addEventListener('resize', handleResize);
541            controller._cleanupFunctions.push(function () { window.removeEventListener('resize', handleResize); });
542            handleResize();
543
544            // #endregion
545
546            // #region Free mode behavior
547
548            // Expand when hovering `OpenTrigger`
549            var onHoverOpenTrigger = function ()
550            {
551                if (controller.mode === "pinned")
552                {
553                    return;
554                }
555
556                panelControl.Action_OpenFold();
557            };
558            controller.context.openTrigger.addEventListener('mouseover', onHoverOpenTrigger);
559            controller._cleanupFunctions.push(function () { controller.context.openTrigger.removeEventListener('mouseover', onHoverOpenTrigger); });
560
561            // Register mousedown inside
562            var mousedownInsidePanel = false;
563            var onMouseDownInsidePanel = function ()
564            {
565                if (controller.mode === "pinned")
566                {
567                    return;
568                }
569
570                mousedownInsidePanel = true;
571                document.addEventListener("mouseup", function () { mousedownInsidePanel = false; }, { once: true, capture: true });
572            };
573            controller.context.panel.addEventListener('mousedown', onMouseDownInsidePanel);
574            controller._cleanupFunctions.push(function () { controller.context.panel.removeEventListener('mousedown', onMouseDownInsidePanel); });
575
576            // Collapse when moving cursor out of `Panel`
577            var onMouseOutPanel = function (e)
578            {
579                if (controller.mode === "pinned")
580                {
581                    return;
582                }
583
584                controller._debug("Panel is free-floating. Checking if mouse outside panel")
585                // Test if the mouse is actually out of the panel
586                if (!controller.context.panel.IsUnderMouse(e, true))
587                {
588                    controller._debug("Mouse outside panel");
589                    if (mousedownInsidePanel)
590                    {
591                        controller._debug("User has mosuedown inside panel & drag outside. Do not fold unless the mousedown is released");
592                        return;
593                    }
594
595                    // Task: https://link.orangelogic.com/Tasks/4G806
596                    // Check if the current mouse out has any text selection as user has drag the mouse outside of the control.
597                    var sameControlTextSelection = false; // Are there any selected text and does the that text selection is inside the element that register this event?
598
599                    // Not working with firefox as it does not support window.getSelection (https://link.orangelogic.com/Tasks/4G2P0)
600                    if (!Utils.IsFirefox())
601                    {
602                        var selection = window.getSelection();
603
604                        sameControlTextSelection = selection !== null && selection.toString() !== "" // text selection exists
605                        if (sameControlTextSelection)
606                        {
607                            sameControlTextSelection = controller.context.panel.contains(selection.baseNode); // the text is select on a child of the registered control
608                        }
609                    }
610
611                    if (!sameControlTextSelection)
612                    {
613                        controller._debug("No text selection. Collapse the panel");
614                        panelControl.Action_CloseFold();
615                    }
616                }
617            };
618            document.addEventListener('mousemove', onMouseOutPanel);
619            controller._cleanupFunctions.push(function () { document.removeEventListener('mousemove', onMouseOutPanel); });
620
621            // Collapse when loosing focus to elements outside panel
622            var onFocusOutPanel = function (e)
623            {
624                if (controller.mode === "pinned")
625                {
626                    return;
627                }
628
629				controller._debug("Panel is free-floating. Checking if loosing focus to elements outside panel");
630                if (!controller.context.panel.contains(e.target))
631                {
632                    controller._debug("Active element is outside panel. Collapse the panel");
633                    panelControl.Action_CloseFold();
634                }
635			};
636            document.addEventListener('focusin', onFocusOutPanel, true);
637            controller._cleanupFunctions.push(function () { document.removeEventListener('focusin', onFocusOutPanel, true); });
638
639
640            // #endregion
641
642            // #region Pinned mode behavior
643
644            // Expand when clicking `OpenTrigger`
645            var onClickOpenTrigger = function ()
646            {
647                if (controller.mode === "free")
648                {
649                    return;
650                }
651
652                panelControl.Action_OpenFold();
653            };
654            controller.context.openTrigger.addEventListener('click', onClickOpenTrigger);
655            controller._cleanupFunctions.push(function () { controller.context.openTrigger.removeEventListener('click', onClickOpenTrigger); });
656
657            // #endregion
658        };
659
660        SidePanelController.prototype._hideElement = function (element)
661        {
662            element.classList.add(StyleMgr.Const.CLASS_INVISIBLE);
663            element.style.setProperty("pointer-events", "none");
664            var a = element.querySelector('a[tabindex]');
665            if (!a)
666            {
667                return;
668            }
669            a.setAttribute('data-original-tabindex', a.getAttribute('tabindex'));
670            a.setAttribute('tabindex', -1);
671        };
672
673        SidePanelController.prototype._showElement = function (element)
674        {
675            element.classList.remove(StyleMgr.Const.CLASS_INVISIBLE);
676            element.style.setProperty("pointer-events", "");
677            var a = element.querySelector('a[tabindex]');
678            if (!a)
679            {
680                return;
681            }
682            a.setAttribute('tabindex', a.getAttribute('data-original-tabindex') || 0);
683            a.removeAttribute('data-original-tabindex');
684        };
685
686        /**
687         * Setup elements based on the mode
688         * @param {SidePanelMode} mode
689         */
690        SidePanelController.prototype._setupElements = function (mode)
691        {
692            var controller = this;
693
694            controller.context.panel.setAttribute(SidePanelController.Const.ATTR_DATA_SIDE_PANEL_MODE, mode);
695
696            if (mode === "pinned")
697            {
698                controller._showElement(controller.context.closeTrigger);
699
700                controller._showElement(controller.context.changeModeTriggers.free);
701                controller._hideElement(controller.context.changeModeTriggers.pinned);
702
703                controller.context.sizer.SetProperty(ConstProperties.Const.MAX_RESIZE_WIDTH, controller.context.sizer.__originalMaxResizeWidth);
704            }
705            else if (mode === "free")
706            {
707                controller._hideElement(controller.context.closeTrigger);
708
709                controller._hideElement(controller.context.changeModeTriggers.free);
710                controller._showElement(controller.context.changeModeTriggers.pinned);
711
712                controller.context.sizer.__originalMaxResizeWidth = controller.context.sizer.GetProperty(ConstProperties.Const.MAX_RESIZE_WIDTH);
713                controller.context.sizer.SetProperty(ConstProperties.Const.MAX_RESIZE_WIDTH, 1);
714
715                // Automatically collapse `Panel` when switching to free mode
716                var panelControl = $Control(controller.context.panel.GetId());
717                panelControl.Action_CloseFold();
718            }
719            // If mode is forced, hide all triggers to change mode
720            if (controller.forcedMode)
721            {
722                controller._hideElement(controller.context.changeModeTriggers.free);
723                controller._hideElement(controller.context.changeModeTriggers.pinned);
724            }
725            controller.context.sizer.ForceHasChanged();
726            PositionMgr.GetInstance().AddToDirtyXList(controller.context.sizer);
727            PositionMgr.GetInstance().CalculatePosition();
728        }
729
730        /**
731         * Change panel mode
732         * 
733         * @param {SidePanelMode} mode Target mode
734         * 
735         * @returns
736         */
737        SidePanelController.prototype.ChangeMode = function (mode)
738        {
739            var controller = this;
740
741            if (!controller._ensure(SidePanelController.Const.SIDE_PANEL_ALL_MODES.includes(mode),
742                'Mode must be any of ["' + SidePanelController.Const.SIDE_PANEL_ALL_MODES.join('","') + '"]. Received: ' + mode))
743            {
744                return;
745            }
746
747            if (controller.mode === mode)
748            {
749                controller._debug("Mode is already ", controller.mode);
750                return;
751            }
752
753            if (controller.forcedMode && controller.forcedMode !== mode)
754            {
755                controller._debug("Mode is forced to ", controller.forcedMode, ". Unable to change to ", mode);
756                return;
757            }
758
759            controller.mode = mode;
760            controller._debug("Mode updated to", mode);
761            if (typeof controller.onModeChange === 'function')
762            {
763                controller.onModeChange(mode);
764            }
765
766            controller._setupElements(mode);
767
768            $.ajax({ type: "GET", url: "htm/AlterSession.aspx?SPMID=" + encodeURIComponent(controller.prefID) + "&SPM=" + controller.mode })
769                .done(function () 
770                {
771                    controller._debug("Updated mode to", mode, "in user preference");
772                })
773                .fail(function ()
774                {
775                    controller._debug("Failed to update mode to", mode, "in user preference");
776                });
777        };
778
779        SidePanelController._debug("SidePanelController loaded");
780
781        window.SidePanelController = SidePanelController;
782    })();
783}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.