@@ -1940,9 +1940,7 @@ export class LCARdSSlider extends LCARdSButton {
19401940 x="0" y="${ yStart } "
19411941 width="${ trackWidth } " height="${ rangeHeight } "
19421942 fill="${ rangeColor } "
1943- opacity="${ rangeOpacity } "
1944- data-range-index="${ idx } "
1945- data-range-label="${ rangeConfig . label || '' } " />
1943+ opacity="${ rangeOpacity } " />
19461944 ` ;
19471945 } else {
19481946 // Horizontal: ranges extend from left to right
@@ -1955,9 +1953,7 @@ export class LCARdSSlider extends LCARdSButton {
19551953 x="${ xStart } " y="0"
19561954 width="${ rangeWidth } " height="${ trackHeight } "
19571955 fill="${ rangeColor } "
1958- opacity="${ rangeOpacity } "
1959- data-range-index="${ idx } "
1960- data-range-label="${ rangeConfig . label || '' } " />
1956+ opacity="${ rangeOpacity } " />
19611957 ` ;
19621958 }
19631959 } ) ;
@@ -2824,6 +2820,16 @@ export class LCARdSSlider extends LCARdSButton {
28242820 trackZoneElement . innerHTML = trackContent ;
28252821 }
28262822
2823+ // Append gauge value-marker indicators into the track zone (on top of ticks).
2824+ // Markers are rendered here — not in the progress zone — so they always land
2825+ // on the tick/gauge area regardless of component layout (default vs picard etc.)
2826+ if ( effectiveMode === 'gauge' && trackZoneElement && trackZone ) {
2827+ const markerSvg = this . _generateGaugeMarkers ( trackZone , orientation ) ;
2828+ if ( markerSvg ) {
2829+ trackZoneElement . innerHTML += markerSvg ;
2830+ }
2831+ }
2832+
28272833 // Inject progress bar into the appropriate zone.
28282834 //
28292835 // z-order is controlled by two named zone elements in the component SVG:
@@ -3008,11 +3014,11 @@ export class LCARdSSlider extends LCARdSButton {
30083014 if ( isVertical ) {
30093015 const yStart = zoneHeight * ( 1 - endPercent ) ;
30103016 const yEnd = zoneHeight * ( 1 - startPercent ) ;
3011- svg += `<rect class="range-bg" x="0" y="${ yStart } " width="${ zoneWidth } " height="${ yEnd - yStart } " fill="${ rangeColor } " opacity="${ rangeOpacity } " data-range-index=" ${ idx } " data-range-label=" ${ rangeConfig . label || '' } " />` ;
3017+ svg += `<rect class="range-bg" x="0" y="${ yStart } " width="${ zoneWidth } " height="${ yEnd - yStart } " fill="${ rangeColor } " opacity="${ rangeOpacity } " />` ;
30123018 } else {
30133019 const xStart = zoneWidth * startPercent ;
30143020 const xEnd = zoneWidth * endPercent ;
3015- svg += `<rect class="range-bg" x="${ xStart } " y="0" width="${ xEnd - xStart } " height="${ zoneHeight } " fill="${ rangeColor } " opacity="${ rangeOpacity } " data-range-index=" ${ idx } " data-range-label=" ${ rangeConfig . label || '' } " />` ;
3021+ svg += `<rect class="range-bg" x="${ xStart } " y="0" width="${ xEnd - xStart } " height="${ zoneHeight } " fill="${ rangeColor } " opacity="${ rangeOpacity } " />` ;
30163022 }
30173023 } ) ;
30183024 return svg ;
@@ -3322,61 +3328,108 @@ export class LCARdSSlider extends LCARdSButton {
33223328 ) ;
33233329 }
33243330 }
3331+ }
33253332
3326- // Render marker indicators for value-based range entries
3327- const markerRanges = ( this . _sliderStyle ?. ranges || [ ] )
3328- . map ( ( r , i ) => ( { range : r , resolvedValue : this . _resolvedMarkerValues [ i ] } ) )
3329- . filter ( ( { range, resolvedValue } ) => 'value' in range && resolvedValue !== null ) ;
3330-
3331- const dMin = this . _displayConfig . min ;
3332- const dMax = this . _displayConfig . max ;
3333-
3334- for ( const { range, resolvedValue } of markerRanges ) {
3335- // Build indicator config: per-range settings override global indicator defaults
3336- const globalIndicator = this . _getIndicatorConfig ( gaugeConfig ) ;
3337- const riCfg = range . indicator || { } ;
3338- const markerColor = this . _resolveColorValue ( riCfg . color || range . color || 'var(--lcars-white, #ffffff)' ) ;
3339- const markerIndicator = {
3340- type : riCfg . type || globalIndicator ?. type || 'line' ,
3341- color : markerColor ,
3342- width : riCfg . size ?. width ?? globalIndicator ?. width ?? 4 ,
3343- height : riCfg . size ?. height ?? globalIndicator ?. height ?? 25 ,
3344- rotation : riCfg . rotation ?? globalIndicator ?. rotation ?? 0 ,
3345- offsetX : riCfg . offset ?. x ?? 0 ,
3346- offsetY : riCfg . offset ?. y ?? 0 ,
3347- borderEnabled : riCfg . border ?. enabled ?? globalIndicator ?. borderEnabled ?? true ,
3348- borderColor : this . _resolveColorValue ( riCfg . border ?. color ?? 'var(--lcars-black, #000000)' ) ,
3349- borderWidth : riCfg . border ?. width ?? globalIndicator ?. borderWidth ?? 1
3350- } ;
3333+ return svg ;
3334+ }
33513335
3352- const markerPercent = Math . max ( 0 , Math . min ( 1 , ( resolvedValue - dMin ) / ( ( dMax - dMin ) || 1 ) ) ) ;
3336+ /**
3337+ * Generate SVG for value-marker indicators, placed in the track/tick zone.
3338+ * Markers are always rendered here (not in the progress zone) so they sit on
3339+ * top of the gauge ticks regardless of component layout (default vs picard, etc.)
3340+ * @param {Object } zoneSpec - The track zone dimensions {x, y, width, height}
3341+ * @param {string } orientation - 'vertical' | 'horizontal'
3342+ * @returns {string } SVG markup
3343+ * @private
3344+ */
3345+ _generateGaugeMarkers ( zoneSpec , orientation ) {
3346+ const isVertical = orientation === 'vertical' ;
3347+ const zoneWidth = zoneSpec . width ;
3348+ const zoneHeight = zoneSpec . height ;
3349+ const gaugeConfig = this . _sliderStyle ?. gauge ;
33533350
3354- if ( isVertical ) {
3355- let baseY = height * ( 1 - markerPercent ) ;
3356- if ( this . _invertFill ) baseY = height * markerPercent ;
3357- const mX = x + ( width / 2 ) + markerIndicator . offsetX ;
3358- const mY = y + baseY + markerIndicator . offsetY ;
3359- svg += this . _renderIndicator (
3360- markerIndicator . type , mX , mY ,
3361- markerIndicator . width , markerIndicator . height , markerIndicator . rotation ,
3362- markerIndicator . color , markerIndicator . borderEnabled ,
3363- markerIndicator . borderColor , markerIndicator . borderWidth , true
3364- ) ;
3365- } else {
3366- let baseX = width * markerPercent ;
3367- if ( this . _invertFill ) baseX = width * ( 1 - markerPercent ) ;
3368- const mX = x + baseX + markerIndicator . offsetX ;
3369- const mY = y + ( height / 2 ) + markerIndicator . offsetY ;
3370- svg += this . _renderIndicator (
3371- markerIndicator . type , mX , mY ,
3372- markerIndicator . width , markerIndicator . height , markerIndicator . rotation ,
3373- markerIndicator . color , markerIndicator . borderEnabled ,
3374- markerIndicator . borderColor , markerIndicator . borderWidth , false
3375- ) ;
3376- }
3351+ const markerRanges = ( this . _sliderStyle ?. ranges || [ ] )
3352+ . map ( ( r , i ) => ( { range : r , resolvedValue : this . _resolvedMarkerValues [ i ] } ) )
3353+ . filter ( ( { range, resolvedValue } ) => 'value' in range && resolvedValue !== null ) ;
3354+
3355+ if ( markerRanges . length === 0 ) return '' ;
3356+
3357+ const dMin = this . _displayConfig . min ;
3358+ const dMax = this . _displayConfig . max ;
3359+ let svg = '' ;
3360+
3361+ // Hardcoded defaults (bottom of fallback chain — preset and per-range config override these):
3362+ // Horizontal: offset.y = 10 (minor tick height). Bordered presets set
3363+ // marker_indicator.offset.y = 20 (border + tick height).
3364+ // Vertical: offset.x = 0. Border size varies; each preset that needs it
3365+ // (e.g. picard) sets the correct value in its marker_indicator block.
3366+
3367+ for ( const { range, resolvedValue } of markerRanges ) {
3368+ const globalIndicator = this . _getIndicatorConfig ( gaugeConfig ) ;
3369+ const riCfg = range . indicator || { } ;
3370+ // Fallback chain: per-range indicator → style.gauge.marker_indicator (preset) → component-aware defaults
3371+ const presetMarker = gaugeConfig ?. marker_indicator || { } ;
3372+ // Color lives under indicator.color for value markers, not range.color
3373+ // (range.color is the band fill — a separate concept)
3374+ const markerColor = this . _resolveColorValue ( riCfg . color || presetMarker . color || 'var(--lcars-white, #ffffff)' ) ;
3375+ const markerIndicator = {
3376+ // type: per-range config first, then triangle default.
3377+ // Deliberately does NOT inherit globalIndicator.type — the main
3378+ // indicator shape is a separate concept from a value marker.
3379+ type : riCfg . type || presetMarker . type || 'triangle' ,
3380+ color : markerColor ,
3381+ width : riCfg . size ?. width ?? presetMarker . size ?. width ?? 20 ,
3382+ height : riCfg . size ?. height ?? presetMarker . size ?. height ?? 20 ,
3383+ rotation : riCfg . rotation ?? presetMarker . rotation ?? 180 ,
3384+ // align: cross-axis position within the track zone.
3385+ // 'start' → left edge (vertical) / top edge (horizontal)
3386+ // 'center' → zone centre
3387+ // 'end' → right edge (vertical) / bottom edge (horizontal)
3388+ // Default is 'start' for all components; offset adjusts tip position.
3389+ align : riCfg . align ?? presetMarker . align ?? 'start' ,
3390+ // Vertical: ticks are on x-axis, default offset unknown (preset must set it)
3391+ // Horizontal: ticks are on y-axis, default offset = 10 (minor tick height)
3392+ offsetX : riCfg . offset ?. x ?? presetMarker . offset ?. x ?? 0 ,
3393+ offsetY : riCfg . offset ?. y ?? presetMarker . offset ?. y ?? ( isVertical ? 0 : 10 ) ,
3394+ borderEnabled : riCfg . border ?. enabled ?? presetMarker . border ?. enabled ?? false ,
3395+ borderColor : this . _resolveColorValue ( riCfg . border ?. color ?? presetMarker . border ?. color ?? 'var(--lcars-black, #000000)' ) ,
3396+ borderWidth : riCfg . border ?. width ?? presetMarker . border ?. width ?? 1
3397+ } ;
3398+
3399+ const markerPercent = Math . max ( 0 , Math . min ( 1 , ( resolvedValue - dMin ) / ( ( dMax - dMin ) || 1 ) ) ) ;
3400+
3401+ if ( isVertical ) {
3402+ // Y: value position along zone height
3403+ let mY = zoneHeight * ( 1 - markerPercent ) ;
3404+ if ( this . _invertFill ) mY = zoneHeight * markerPercent ;
3405+ // X: cross-axis position determined by align, then fine-tuned by offsetX
3406+ const alignX = markerIndicator . align === 'start' ? 0
3407+ : markerIndicator . align === 'end' ? zoneWidth
3408+ : zoneWidth / 2 ; // 'center'
3409+ const mX = alignX + markerIndicator . offsetX ;
3410+ svg += this . _renderIndicator (
3411+ markerIndicator . type , mX , mY + markerIndicator . offsetY ,
3412+ markerIndicator . width , markerIndicator . height , markerIndicator . rotation ,
3413+ markerIndicator . color , markerIndicator . borderEnabled ,
3414+ markerIndicator . borderColor , markerIndicator . borderWidth , true
3415+ ) ;
3416+ } else {
3417+ // X: value position along zone width
3418+ let mX = zoneWidth * markerPercent ;
3419+ if ( this . _invertFill ) mX = zoneWidth * ( 1 - markerPercent ) ;
3420+ // Y: cross-axis position determined by align, then fine-tuned by offsetY
3421+ const alignY = markerIndicator . align === 'start' ? 0
3422+ : markerIndicator . align === 'end' ? zoneHeight
3423+ : zoneHeight / 2 ; // 'center'
3424+ const mY = alignY + markerIndicator . offsetY ;
3425+ svg += this . _renderIndicator (
3426+ markerIndicator . type , mX + markerIndicator . offsetX , mY ,
3427+ markerIndicator . width , markerIndicator . height , markerIndicator . rotation ,
3428+ markerIndicator . color , markerIndicator . borderEnabled ,
3429+ markerIndicator . borderColor , markerIndicator . borderWidth , false
3430+ ) ;
33773431 }
33783432 }
3379-
33803433 return svg ;
33813434 }
33823435
0 commit comments