1/** 2 * @file FieldPanel.js 3 * @description A layout-only web component that provides the gray panel chrome used in the bulk 4 * editor sidebar (background, padding, optional header). All content is slotted and stays in the 5 * light DOM â fully queryable from outside. No business logic or internal state. 6 * 7 * @class FieldPanel 8 * @extends WebComponent 9 * 10 * @example 11 * <!-- Basic panel with header --> 12 * <field-panel-element title="Pricing" subtitle="Set price and fees"> 13 * <form-field-element type="number" label="Price" prefix="$"></form-field-element> 14 * </field-panel-element> 15 * 16 * @example 17 * <!-- Bulk editor panel (activated by sidebar JS via data-field) --> 18 * <field-panel-element data-field="status" data-size="half" title="Status"> 19 * <form-field-element type="dropdown" label="Ticket status"></form-field-element> 20 * </field-panel-element> 21 * 22 * @attribute {string} title - Bold header title (plain text, HTML-escaped) 23 * @attribute {string} subtitle - Gray subtext rendered below the title 24 * @attribute data-field - Identifies the bulk editor field; read by sidebar JS to show/hide the panel 25 * @attribute data-size - 'half' | 'full' â controls grid column span in the values grid 26 */ 27import { WebComponent } from '../Components/Base/WebComponent.js'; 28 29class FieldPanel extends WebComponent { 30 static get observedAttributes() { 31 return ['title', 'subtitle']; 32 } 33 34 _built = false; 35 36 constructor() { 37 super(); 38 this.loadBaseCSS(); 39 } 40 41 connectedCallback() { 42 if (this._built) return; 43 this._built = true; 44 this.render(); 45 } 46 47 attributeChangedCallback(name, oldVal, newVal) { 48 if (oldVal === newVal || !this._built) return; 49 this.render(); 50 } 51 52 render() { 53 const title = this.getAttribute('title') || ''; 54 const subtitle = this.getAttribute('subtitle') || ''; 55 56 this.loadTemplate(` 57 <style>${this._styles()}</style> 58 <div class="fp-panel"> 59 ${title ? ` 60 <div class="fp-header flex flex-column gap-4"> 61 <span class="fp-title">${this._esc(title)}</span> 62 ${subtitle ? `<span class="fp-subtitle font-14">${this._esc(subtitle)}</span>` : ''} 63 </div>` : ''} 64 <slot></slot> 65 </div> 66 `); 67 } 68 69 _esc(str) { 70 return String(str || '') 71 .replace(/&/g, '&') 72 .replace(/</g, '<') 73 .replace(/>/g, '>') 74 .replace(/"/g, '"'); 75 } 76 77 _styles() { 78 return ` 79 /* Top-level bulk editor field: hidden until activated by sidebar JS */ 80 :host([data-field]) { display: none; } 81 :host([data-field].is-active) { display: block; } 82 /* Full-width span in the values grid */ 83 :host([data-size="full"]), :host(.is-col-full) { grid-column: 1 / -1; } 84 85 .fp-panel { 86 background: #F5F6F6; 87 border-radius: 6px; 88 padding: 24px; 89 } 90 91 .fp-header { 92 margin-bottom: 16px; 93 } 94 95 .fp-title { 96 font-weight: 600; 97 } 98 99 /* subtitle uses .font-14 from base CSS; color override here */ 100 .fp-subtitle { 101 color: #5E6463; 102 } 103 `; 104 } 105} 106 107if (!customElements.get('field-panel-element')) {
108 customElements.define('field-panel-element', FieldPanel); 109}
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.