Svelte 中的分段控制组件——如何获取同级组件的宽度和偏移量

问题描述

我正在尝试编写一个 svelte 组件,它的行为类似于分段控制菜单(如连续性部分下的 here)。由于我是 svelte 的新手,为了使任务更容易,我重新使用了一些 IBM 的 open-sourced code

这是目前的代码

// App.js
<script>
  import { SegmentedControl,Segment,SegmentBackground } from './components/SegmentedControl';
</script>

<SegmentedControl>
  <Segment text="Latest news" id='segment-latest-news' />
  <Segment text="Trending" id='segment-trending' />
  <SegmentBackground />
</SegmentedControl>
// SegmentedControl.svelte
<script>
  export let selectedindex = 0;

  import { afterUpdate,createEventdispatcher,setContext } from 'svelte';
  import { writable } from 'svelte/store';

  const dispatch = createEventdispatcher();
  const currentId = writable(null);

  $: currentIndex = -1;
  $: segments = [];
  $: if (segments[currentIndex]) {
    dispatch('change',currentIndex);
    currentId.set(segments[currentIndex].id);
  }

  setContext('SegmentedControl',{
    currentId,add: ({ id,text,selected }) => {
      if (selected) {
        selectedindex = segments.length;
      }
      segments = [...segments,{ id,selected }];
    },update: (id) => {
      selectedindex = segments.map(({ id }) => id).indexOf(id);
    },});

  afterUpdate(() => {
    if (selectedindex !== currentIndex) {
      currentIndex = selectedindex;
    }
  });
</script>

<div
  role='tablist'
  class:segmented-control='{true}'
  {...$$restProps}
  on:click
>
  <slot />
</div>
// Segment.svelte
<script>
  export let text = '';
  export let selected = false;
  export let disabled = false;
  export let id = '';
  export let ref = null;

  import { afterUpdate,getContext,onDestroy } from 'svelte';

  const ctx = getContext('SegmentedControl');

  ctx.add({ id,selected });

  const unsubscribe = ctx.currentId.subscribe(($) => {
    selected = $ === id;
  });

  afterUpdate(() => {
    if (selected) {
      ref.focus();
    }
  });

  onDestroy(() => {
    unsubscribe();
  });
</script>

<button
  bind:this='{ref}'
  role='tab'
  tabindex='{selected ? '0' : '-1'}'
  aria-selected='{selected}'
  disabled='{disabled}'
  id='{id}'
  class:segmented-control-btn='{true}'
  class:segmented-control--selected='{selected}'
  {...$$restProps}
  on:click
  on:click|preventDefault='{() => {
    ctx.update(id);
  }}'
>
  <span class:segmented-control__label='{true}'>
    <slot>{text}</slot>
  </span>
</button>
// SegmentBackground.svelte (practically empty)
<script></script>

<div class:segmented-control--bg='{true}'></div>

<style>
  .segmented-control--bg {
    position: absolute;
    top: .175em;
    left: .175em;
    z-index: -1;
    height: 2em;
    min-width: 2em;
    background: #111;
    transition: 300ms;
  }
</style>

我已将 SegmentBackground 组件添加到 IBM 的代码中,我希望它更改 widthoffsetLeft 以匹配选定的 Segment 组件。但我不知道如何访问这些数据。

我已经有一段时间不知道如何解决这个问题了。如果有人能告诉或暗示如何去做,我将不胜感激。

谢谢

编辑:或者为 SegmentedControl 组件添加背景可能更好?

解决方法

我解决这个问题的方法是还将对按钮元素的引用传递给 SegmentedControl,一旦你有了这个,你就可以将 selectedIndex 和这个引用结合到获取实际的活动按钮。从按钮使用 getBoundingClientRect 获取按钮的位置,并以某种方式将其传递给背景,以便它可以自行定位。

检查这个 REPL 以获得快速和肮脏的实现。