익스클루시브

Exposure <mv-exposure>

공유 및 개인정보 설정(공유 대화상자, 게시물 공개 범위, 프로필 공개 설정, 프로젝트 공개 범위, 캘린더 공유, 파일 링크)을 위한 노출 게이지입니다. 감싼 컨트롤을 읽어 사용자가 확정하기 전에 얼마나 노출되는지 알려 줍니다.

문화적 레퍼런스

Thief: The Dark Project, Looking Glass Studios (1998, 게임). 화면의 표시기가 플레이어가 얼마나 눈에 띄는지를 숨은 상태부터 완전히 노출된 상태까지 매 순간 보여 주므로, 한 걸음 내딛기 전에 노출 정도를 알 수 있습니다. UI에서는 그 메커니즘만 중립적인 아이콘 타일과 단계형 게이지로 가져왔습니다. 공유 또는 개인정보 폼의 컨트롤이 하나의 수치를 만들어 내며, 이 수치는 공개 대상이 넓어질수록 올라가고, 누가 항목을 볼 수 있는지 명시하며, 수치를 높이는 요인을 클릭 한 번으로 줄일 수 있는 방법과 함께 나열하고, 가장 넓은 구간으로 가는 것은 확인을 거치는 의도적인 단계로 만듭니다.

작동 방식

공유 및 개인정보 설정(공유 대화상자, 게시물 공개 범위, 프로필 공개 설정, 프로젝트 공개 범위, 캘린더 공유, 파일 링크)을 위한 공개 범위 게이지로, 감싼 컨트롤을 읽어 확정하기 전에 얼마나 노출되는지 알려 줍니다. 라디오, 옵션, 스위치, 텍스트 필드, 사용자 목록에는 선언적 점수 맵을 지정합니다. data-exposure="3"은 공개 대상 수준(가장 넓은 것이 적용됨)이고, data-exposure="+0.4"는 공개 대상을 바꾸지 않고 해당 구간 안에서 수치를 움직이는 수정자(만료되지 않는 링크, 다운로드 허용, 외부 게스트)입니다. score(state) 함수로 계산 전체를 대신할 수도 있습니다. 결과는 “Only you”부터 “Public on the internet”까지 차분하게 표시되는 하나의 수치입니다. 공개 대상이 넓을수록 색이 짙어지는 아이콘 타일, 구간마다 높아지는 단계형 트랙, 글로 표현한 구간, 그리고 명시된 공개 대상(“Visible to: you, 4 people, and anyone at Acme with the link”)으로 구성됩니다. 그 아래에는 실제로 수치를 높이는 요인이 영향도 순으로 나열되며, 각각 클릭 한 번으로 줄이는 방법(“The link never expires → Expire in 7 days”)이 함께 제공됩니다. 이 방법은 실제 컨트롤을 바꾸고 input/change 이벤트를 발생시킵니다. 더 높은 구간으로 올라가면 취소 가능한 mv-exposure-raise가 발생하며(워크스페이스 정책이 이를 거부하면 컨트롤이 복원됨), confirm 이상의 구간은 빗금과 테두리로 표시된 보류 상태로 남아 인라인 또는 플로팅 확인을 요구하고, 확인하거나 되돌릴 때까지 네이티브 폼 제출을 막습니다. 변형은 세 가지입니다. bar(요인까지 포함한 전체 표시), badge(툴바와 작성 창을 위한 작은 상태 칩으로, for로 다른 영역을 지켜볼 수 있음), inline(작은 단계형 게이지가 들어간 문장)입니다.

카테고리폼
유형Web Component (<mv-exposure>)
상태안정
키트신뢰와 개인정보 보호
함께 설치되는 항목button
Keywordsexclusive, culture, privacy, sharing, share-dialog, visibility, audience, permissions, access, public, link-sharing, gauge, meter, risk, confirmation, policy, settings, form

When to use

  • A share dialog must make clear who can open a document or file before the user copies the link or clicks Done
  • A post, profile or event audience selector should spell out the real reach, not just the option name
  • Several small settings (link expiry, downloads, guests) add up to more exposure than any single control suggests
  • Making something public must be deliberate and confirmed, or vetoed by an organization policy

Avoid when

  • You just need to show a measured quantity in a range (storage used, a score, completion) → use Progress instead
  • Access is temporary and elevated (admin mode, a time-boxed share) and must end on time → use Midnight instead
  • There is a single public/private toggle with no nuance: a switch with a clear description says it all → use Switch instead

설치

node scripts/add.mjs exposure --out ./src/marvelous

Marvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["exposure"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

복사되는 파일(의존성 포함): tokens/tokens.css, core/base.css, components/button/button.css, core/dom.js, core/element.js, core/motion.js, core/position.js, components/exposure/exposure.js, components/exposure/exposure.css.

사용법

빠른 시작, 동작하는 가장 작은 마크업:

<mv-exposure org="Acme">
  <select aria-label="Share with">
    <option data-exposure="0">Only me</option>
    <option data-exposure="2" selected>Everyone at Acme</option>
    <option data-exposure="4">Public on the internet</option>
  </select>
</mv-exposure>

기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:

<div id="ex-demo" style="width:min(100%,62rem);margin-inline:auto">
  <style>
    #ex-demo { display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr); gap:1.25rem; align-items:start }
    #ex-demo .ex-card { display:grid; gap:1rem; padding:1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-sm) }
    #ex-demo .ex-side { display:grid; gap:1.25rem; align-content:start }
    #ex-demo .ex-head h3, #ex-demo .ex-head h4 { margin:0; font-size:1rem; font-weight:650; letter-spacing:-.01em }
    #ex-demo .ex-head h4 { font-size:.9375rem }
    #ex-demo .ex-head p { margin:.125rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem }
    #ex-demo .ex-form { display:grid; gap:1rem; margin:0 }
    #ex-demo .ex-label { display:block; margin-bottom:.375rem; color:var(--mv-fg); font-size:.8125rem; font-weight:600 }
    #ex-demo .ex-people { display:grid; gap:.125rem; margin:0; padding:0; list-style:none }
    #ex-demo .ex-person { display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:center; gap:.625rem; padding:.3125rem 0; font-size:.8125rem }
    #ex-demo .ex-person b { display:block; font-weight:550 }
    #ex-demo .ex-person small { display:block; overflow:hidden; color:var(--mv-fg-muted); font-size:.75rem; text-overflow:ellipsis; white-space:nowrap }
    #ex-demo .ex-role { display:flex; align-items:center; gap:.375rem; color:var(--mv-fg-muted); font-size:.75rem }
    #ex-demo .ex-access .mv-choice-description { font-size:.75rem }
    #ex-demo .ex-row { display:grid; grid-template-columns:minmax(0,11rem) minmax(0,1fr); gap:1rem; align-items:end }
    #ex-demo .ex-row .mv-choice { padding-bottom:.125rem }
    #ex-demo .ex-foot { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; padding-top:1rem; border-top:1px solid var(--mv-border) }
    #ex-demo .ex-foot .mv-choice { font-size:.8125rem }
    #ex-demo .ex-actions { display:flex; gap:.5rem; margin-inline-start:auto }
    #ex-demo .ex-log { margin:0; min-height:1.1rem; color:var(--mv-fg-subtle); font:.75rem/1.45 var(--mv-font-mono) }
    #ex-demo .ex-post { display:grid; gap:.75rem }
    #ex-demo .ex-post-tools { display:flex; align-items:center; gap:.75rem; flex-wrap:wrap }
    #ex-demo .ex-post-tools .mv-select { width:auto; min-width:10.5rem }
    #ex-demo .ex-post-tools .mv-choice { font-size:.8125rem }
    #ex-demo .ex-post-foot { display:flex; align-items:center; justify-content:space-between; gap:.75rem; padding-top:.75rem; border-top:1px solid var(--mv-border) }
    #ex-demo .ex-cal { display:grid; gap:.875rem }
    #ex-demo .ex-cal-grid { display:grid; grid-template-columns:1fr 1fr; gap:.75rem }
    #ex-demo .ex-cal-reading { padding:.625rem .75rem; border-radius:var(--mv-radius-md); background:var(--mv-bg-subtle); font-size:.8125rem; line-height:1.5 }
    @media (max-width:52rem) { #ex-demo { grid-template-columns:minmax(0,1fr) } }
    @media (max-width:30rem) { #ex-demo .ex-row, #ex-demo .ex-cal-grid { grid-template-columns:minmax(0,1fr) } }
  </style>

  <!-- 1 · Share dialog: bar variant, reading on top, factors with one-click reductions, public needs confirmation -->
  <section class="ex-card" aria-labelledby="ex-share-title">
    <div class="ex-head">
      <h3 id="ex-share-title">Share “Q4 launch plan”</h3>
      <p>Acme · Marketing › Launches · edited by Maya Chen 2 hours ago</p>
    </div>
    <mv-exposure id="ex-share" org="Acme" confirm="4" label="Who can see this document">
      <form class="ex-form" id="ex-share-form">
        <div data-exposure-reading></div>

        <div>
          <span class="ex-label" id="ex-people-label">People with access</span>
          <ul class="ex-people" aria-labelledby="ex-people-label" data-exposure="1" data-exposure-count=".ex-person" data-exposure-audience="{count} people" data-exposure-audience-one="1 person">
            <li class="ex-person">
              <span class="mv-avatar" data-size="sm" data-variant="tinted" aria-hidden="true"><span class="mv-avatar-fallback">MC</span></span>
              <span><b>Maya Chen (you)</b><small>[email protected]</small></span>
              <span class="ex-role">Owner</span>
            </li>
            <li class="ex-person">
              <span class="mv-avatar" data-size="sm" data-variant="tinted" aria-hidden="true"><span class="mv-avatar-fallback">DA</span></span>
              <span><b>Diego Alvarez</b><small>[email protected]</small></span>
              <span class="ex-role">Editor</span>
            </li>
            <li class="ex-person">
              <span class="mv-avatar" data-size="sm" data-variant="tinted" aria-hidden="true"><span class="mv-avatar-fallback">PN</span></span>
              <span><b>Priya Natarajan</b><small>[email protected]</small></span>
              <span class="ex-role">Commenter</span>
            </li>
            <li class="ex-person" id="ex-guest" data-exposure="+0.2" data-exposure-factor="A guest outside Acme has access" data-exposure-reduce-label="Remove guest">
              <span class="mv-avatar" data-size="sm" data-variant="tinted" aria-hidden="true"><span class="mv-avatar-fallback">JO</span></span>
              <span><b>Jordan Okafor</b><small>[email protected]</small></span>
              <span class="ex-role"><span class="mv-badge" data-variant="warning" data-size="sm">Guest</span>
                <button type="button" class="mv-button" data-variant="ghost" data-size="icon" data-exposure-remove aria-label="Remove Jordan Okafor" style="width:1.75rem;height:1.75rem">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>
                </button>
              </span>
            </li>
          </ul>
        </div>

        <fieldset class="mv-choice-group ex-access">
          <legend>General access</legend>
          <label class="mv-choice">
            <input type="radio" class="mv-radio" name="access" value="restricted" data-exposure="0">
            <span class="mv-choice-text"><span class="mv-choice-title">Restricted</span><span class="mv-choice-description">Only the people above can open it</span></span>
          </label>
          <label class="mv-choice">
            <input type="radio" class="mv-radio" name="access" value="org" checked data-exposure="2" data-exposure-audience="anyone at Acme with the link" data-exposure-factor="Anyone at Acme with the link can open it" data-exposure-reduce="restricted" data-exposure-reduce-label="Restrict">
            <span class="mv-choice-text"><span class="mv-choice-title">Acme</span><span class="mv-choice-description">Anyone at Acme with the link</span></span>
          </label>
          <label class="mv-choice">
            <input type="radio" class="mv-radio" name="access" value="link" data-exposure="3" data-exposure-audience="anyone with the link" data-exposure-factor="Anyone with the link can open it, no sign-in" data-exposure-reduce="org" data-exposure-reduce-label="Limit to Acme">
            <span class="mv-choice-text"><span class="mv-choice-title">Anyone with the link</span><span class="mv-choice-description">No sign-in required</span></span>
          </label>
          <label class="mv-choice">
            <input type="radio" class="mv-radio" name="access" value="public" data-exposure="4" data-exposure-audience="anyone on the internet" data-exposure-factor="Listed publicly and indexed by search engines" data-exposure-reduce="link" data-exposure-reduce-label="Unlist" data-exposure-confirm="Anyone can find “Q4 launch plan” in search results and open it without signing in, including unreleased dates and pricing." data-exposure-confirm-label="Make public">
            <span class="mv-choice-text"><span class="mv-choice-title">Public on the web</span><span class="mv-choice-description">Searchable, no sign-in required</span></span>
          </label>
        </fieldset>

        <div class="ex-row">
          <div>
            <label class="ex-label" for="ex-expires">Link expires</label>
            <select class="mv-select" id="ex-expires" name="expires" data-size="sm">
              <option value="never" selected data-exposure="+0.4" data-exposure-factor="The link never expires" data-exposure-reduce="7d" data-exposure-reduce-label="Expire in 7 days">Never</option>
              <option value="30d" data-exposure="+0.2" data-exposure-factor="The link stays valid for 30 days" data-exposure-reduce="7d" data-exposure-reduce-label="Shorten to 7 days">In 30 days</option>
              <option value="7d">In 7 days</option>
              <option value="1d">In 24 hours</option>
            </select>
          </div>
          <label class="mv-choice" data-control="end">
            <input type="checkbox" role="switch" class="mv-switch" name="download" checked data-exposure="+0.2" data-exposure-factor="Viewers can download, print and copy">
            <span class="mv-choice-text"><span class="mv-choice-title">Allow downloads</span><span class="mv-choice-description">Viewers can download, print and copy</span></span>
          </label>
        </div>

        <div class="ex-foot">
          <label class="mv-choice"><input type="checkbox" role="switch" class="mv-switch" data-size="sm" id="ex-policy"> Workspace policy: no public documents</label>
          <span class="ex-actions">
            <button type="button" class="mv-button" data-variant="outline" data-size="sm" id="ex-reset">Reset</button>
            <button type="submit" class="mv-button" data-size="sm">Done</button>
          </span>
        </div>
      </form>
    </mv-exposure>
    <p class="ex-log" id="ex-log" aria-live="off">Pick “Public on the web”, or reduce a factor.</p>
  </section>

  <div class="ex-side">
    <!-- 2 · Post composer: badge variant watching the composer with `for`, custom bands -->
    <section class="ex-card" aria-labelledby="ex-post-title">
      <div class="ex-head">
        <h4 id="ex-post-title">New post</h4>
        <p>Posting as Maya Chen</p>
      </div>
      <div class="ex-post" id="ex-post">
        <textarea class="mv-textarea" rows="3" aria-label="Post text">Shipped the new onboarding flow today. Huge thanks to Diego and Priya for three weeks of late-night testing!</textarea>
        <div class="ex-post-tools">
          <select class="mv-select" data-size="sm" aria-label="Audience">
            <option value="me" data-exposure="0">Only me</option>
            <option value="close" data-exposure="1" data-exposure-audience="12 close friends">Close friends</option>
            <option value="friends" selected data-exposure="2" data-exposure-audience="your 348 friends">Friends</option>
            <option value="fof" data-exposure="3" data-exposure-audience="their friends, about 41,000 people">Friends of friends</option>
            <option value="public" data-exposure="4" data-exposure-audience="anyone, on or off the app" data-exposure-confirm="Anyone can see, share and embed this post, even without an account." data-exposure-confirm-label="Post publicly">Public</option>
          </select>
          <label class="mv-choice"><input type="checkbox" class="mv-checkbox" data-exposure="+0.3" data-exposure-factor="Your location is attached"> Add location · San Francisco</label>
        </div>
      </div>
      <div class="ex-post-foot">
        <mv-exposure for="ex-post" id="ex-badge" variant="badge" confirm="4" label="Post audience" bands="Only you, Close friends, Friends, Friends of friends, Public"></mv-exposure>
        <button type="button" class="mv-button" data-size="sm">Post</button>
      </div>
    </section>

    <!-- 3 · Calendar sharing: inline variant, reading placed where the author wants it -->
    <section class="ex-card" aria-labelledby="ex-cal-title">
      <div class="ex-head">
        <h4 id="ex-cal-title">Calendar sharing</h4>
        <p>Work calendar · Maya Chen · Pacific Time</p>
      </div>
      <mv-exposure variant="inline" org="Acme" label="Calendar visibility" class="ex-cal">
        <div class="ex-cal-grid">
          <div>
            <label class="ex-label" for="ex-cal-who">Share with</label>
            <select class="mv-select" id="ex-cal-who" data-size="sm">
              <option value="none" data-exposure="0">Nobody</option>
              <option value="team" selected data-exposure="1" data-exposure-audience="your 8 teammates">My team</option>
              <option value="org" data-exposure="2" data-exposure-audience="everyone at Acme">Everyone at Acme</option>
              <option value="public" data-exposure="4" data-exposure-audience="anyone with the public URL">Public URL</option>
            </select>
          </div>
          <div>
            <label class="ex-label" for="ex-cal-detail">They see</label>
            <select class="mv-select" id="ex-cal-detail" data-size="sm">
              <option value="busy">Free/busy only</option>
              <option value="titles" data-exposure="+0.4">Event titles</option>
              <option value="all" selected data-exposure="+0.8">All details</option>
            </select>
          </div>
        </div>
        <div class="ex-cal-reading" data-exposure-reading></div>
      </mv-exposure>
    </section>
  </div>

  <script type="module">
    const share = document.getElementById("ex-share");
    const form = document.getElementById("ex-share-form");
    const log = document.getElementById("ex-log");
    const policy = document.getElementById("ex-policy");
    const expires = document.getElementById("ex-expires");
    const guest = document.getElementById("ex-guest");
    const badge = document.getElementById("ex-badge");
    const say = (t) => { log.textContent = t; };

    // Icons that fit a social audience better than the default document scale.
    badge.bands = [
      { label: "Only you", icon: "lock" },
      { label: "Close friends", icon: "people" },
      { label: "Friends", icon: "people" },
      { label: "Friends of friends", icon: "link" },
      { label: "Public", icon: "globe" },
    ];

    // A restricted document has no link: its expiry no longer matters.
    const syncExpiry = () => { expires.disabled = form.elements.access.value === "restricted"; };
    form.addEventListener("change", syncExpiry);
    syncExpiry();

    // The guest chip's own remove button (the reduction runs it too).
    const removeGuest = guest.querySelector("[data-exposure-remove]");
    removeGuest.addEventListener("click", () => guest.remove());

    share.addEventListener("mv-exposure-raise", (e) => {
      if (policy.checked && e.detail.to === 4) {
        e.preventDefault();
        say("Blocked by workspace policy: public documents are turned off.");
        return;
      }
      say(`Raise: ${e.detail.fromLabel} → ${e.detail.toLabel}${e.detail.needsAck ? " (needs confirmation)" : ""}`);
    });
    share.addEventListener("mv-exposure-change", (e) => {
      say(`${e.detail.acknowledged ? "Confirmed" : "Now"}: ${e.detail.label} · ${e.detail.value.toFixed(1)} of 4`);
    });
    form.addEventListener("submit", (e) => {
      e.preventDefault();
      if (!share.pending) say(`Saved: ${share.bandLabel}. ${share.audience}.`);
    });

    // Reset the scenario (and put the guest back).
    const initial = { access: "org", expires: "never", download: true };
    document.getElementById("ex-reset").addEventListener("click", () => {
      if (!guest.isConnected) share.querySelector(".ex-people").append(guest);
      form.elements.access.value = initial.access;
      form.elements.download.checked = initial.download;
      syncExpiry();
      expires.value = initial.expires;
      share.refresh();
      say("Scenario reset.");
    });
  </script>
</div>

API

Attributes

Name유형DefaultDescription
variantbar | badge | inlinebarbar: the full reading (icon tile, band, audience, stepped track, scale, factors) in a subtle card. badge: a compact chip with the icon tile, the band and the audience, no card, factors hidden, acknowledgement floats. inline: one sentence led by a tiny stepped gauge, factors hidden, acknowledgement floats.
orgstringOrganization name used in the default band “Anyone at {org}” and in {org} placeholders of audience and factor texts. Without it the band reads “Anyone in your organization”.
confirmband index | "top" | (empty)Bands at or above this index need an explicit acknowledgement before they apply; empty or "top" means the last band only. Without the attribute nothing needs confirming (mv-exposure-raise still fires).
foridWatch another element (a form, a composer) instead of the component’s children; the component then only holds the reading. Resolved in the component’s root node when it connects or when the attribute changes.
bandscomma-separated labelsOnly you, Specific people, Anyone at {org}, Anyone with the link, Public on the internetBand labels from the narrowest to the widest audience (at least 2). Icons are picked along lock, people, building, link, globe; use the bands property to choose them.
labelstringVisibilityAccessible name of the meter, also shown as the small caption above the band in the bar variant.
show-factors"true" | "false"Show or hide the factor list. Default: shown in bar, hidden in badge and inline.
data-exposurenumber | +number | -numberOn any radio, checkbox, switch, option, text field or element in the watched region. A plain number is an audience level, active when the control is checked/selected/filled (or the element is present and not hidden); the highest active level sets the band. A signed number is a modifier: modifiers add up and move the reading inside the band (capped just below the next band), never across it. Disabled, hidden or inert contributors are ignored.
data-exposure-audiencestringWords added to the audience sentence while the contributor is active (“anyone at {org} with the link”). {count} and {org} are replaced; data-exposure-audience-one is used when the count is 1.
data-exposure-countCSS selectorOn a container (a people list): the number of matching descendants that are not hidden. The container is active when the count is above 0 and {count} is available to its audience text.
data-exposure-factorstringMakes an active contributor a listed factor (“The link never expires”) whenever removing it would lower the reading. Factors are sorted by impact.
data-exposure-reducestringThe one-click reduction of a factor. Radio: value of the radio to check instead. Option: value to select in its select. Checkbox or switch: unchecked (no value needed). Text field: the value to set (empty clears it). Other elements: the reduction clicks their [data-exposure-remove] button, so your own removal logic runs.
data-exposure-reduce-labelstringLabel of the reduction button (“Expire in 7 days”). Defaults: “Switch to {option}”, “Turn off”, “Clear”, “Remove”.
data-exposure-confirm / data-exposure-confirm-labelstringOn the contributor that opens a guarded band: the acknowledgement text (who can reach it and what that means) and the confirm button label (“Make public”).
data-exposure-reading(marker)On an empty child: the reading is rendered inside it instead of being appended at the end of the component.
data-band / data-pendingset by the componentCurrent band index and pending state, on the host (styleable).

Properties

Name유형Description
valuenumberCurrent reading, from 0 to bands.length - 1 (read-only).
band / bandLabelnumber / stringIndex and label of the current band (read-only).
audiencestringThe audience sentence, e.g. “Visible to: you, 4 people, and anyone at Acme with the link” (read-only).
factorsArray<{ label, impact, element, reduceLabel }>Factors raising the reading, most impactful first (read-only).
pendingbooleanTrue while a guarded band waits for acknowledge() or revert() (read-only). Check it in your own save handler if the save does not go through a native form submission.
bandsArray<string | { label, icon? }>Band list; icon is one of lock, people, building, link, globe. Reading it returns the resolved objects. Setting a string sets the attribute.
score(state) => number | { value?, audience?, factors? } | voidCustom scoring. state: { values (controls by name: radio value, checkbox boolean or array, select value), value, band, bands, audience (string[]), factors, contributors }. Return a number to replace the reading, or an object to replace the value, the audience parts and/or the factors ({ label, impact?, reduceLabel?, reduce?: () => void, confirm? }).
stringsPartial<Record<string, string>>Overrides for every visible text and announcement (keys: label, you, orgFallback, visibleTo ({list}), factorsTitle, pending, ackTitle ({band}), ackText, ackConfirm, ackCancel, reduceTo ({option}), reduceToggle, reduceRemove, reduceClear, valueText, valueTextPending, announceUp, announceDown, announceSame, announcePending, announceConfirmed, announceReverted, announceBlocked, announceSubmit). English defaults.

Methods

NameDescription
refresh()Re-reads the controls. Needed only after changing them from code without firing input/change events (frameworks setting .checked or .value); DOM changes (chips added or removed, hidden, disabled) are observed automatically.
acknowledge()Accepts the pending band (same as the confirm button). Returns false when nothing is pending.
revert()Restores every watched form control to the last accepted state and fires their input/change events; drops a pending band. Changes made outside form controls (a chip added by the app) cannot be undone by the component.
reduce(indexOrElement)Applies a factor’s reduction from code (index in factors, or its contributor element). Emits the cancelable mv-exposure-reduce; returns false if cancelled or impossible.

Events

NameDescription
mv-exposure-raiseCancelable, before a rise into a higher band is applied. detail: { from, to, fromLabel, toLabel, value, audience, needsAck, source (the control that changed, or null) }. preventDefault() vetoes it: the controls go back to the last accepted state and the refusal is announced.
mv-exposure-changeThe accepted reading changed (band, value, audience or factors). detail: { value, band, label, audience, factors: [{ label, impact }], acknowledged }. Not emitted for the initial reading.
mv-exposure-reduceCancelable, before a factor’s reduction runs. detail: { label, impact, element }.

Content structure

NameDescription
(content)The sharing or privacy controls to read (unless for is set). The component never restyles them; it only reads them and, for reductions or reverts, sets their value and fires input/change.

CSS classes

NameDescription
mv-exposureThe reading (data-variant, data-band, data-top, data-pending, data-flare during a rise).
mv-exposure-gaugerole="meter" wrapper: -head (-mark with -mark-icon, -caption, -band, -tag, -audience), -track (-step, -step-fill; data-lit), -scale.
mv-exposure-factorsFactor list: -factors-title, -list, -factor (-factor-icon, -factor-label, .mv-exposure-reduce button).
mv-exposure-ackAcknowledgement group: -ack-icon, -ack-title, -ack-text, -ack-actions (.mv-exposure-cancel, .mv-exposure-confirm). data-floating when shown as a popover (badge and inline).

CSS variables

NameDefaultDescription
--mv-exposure-lowvar(--mv-fg-subtle)Tone of the narrowest audience: the neutral end of the scale.
--mv-exposure-highvar(--mv-warning)Tone the reading warms up to as the audience widens (bands are mixed between low and high).
--mv-exposure-peakcolor-mix(warning, danger 55%)Tone of the widest band, the pending tag and the acknowledgement.

Accessibility

The reading is a role="meter" named by label (“Visibility”), with aria-valuenow/min/max and an aria-valuetext that is the whole reading in words: “Anyone at Acme. Visible to: you, 4 people, and anyone at Acme with the link.” (with “needs confirmation” while pending). Its visible children are presentational, so screen readers get one coherent sentence instead of fragments. Band changes are announced once in a polite live region, debounced so arrowing through a radio group does not chatter (“Visibility raised to Anyone with the link. …”); a guarded band, a veto and a blocked submission are announced assertively. Focus is never moved when a control changes (arrow keys change radios immediately, so stealing focus would break keyboard use): the acknowledgement appears right after the reading in the DOM, as a labelled and described group with “Keep previous setting” and the confirm button; Escape inside it keeps the previous setting, and after either choice focus returns to the control that raised the band. Submitting the surrounding form while pending is prevented and moves focus to the confirm button. Reduction buttons are real buttons whose accessible name includes the factor (“Expire in 7 days: The link never expires”); after a reduction focus moves to the next factor (or to the meter) and the new reading is announced. The level is never carried by color alone: each band has its own words and icon (lock, people, building, link, globe), the steps grow in height, and a pending band adds a dashed ring, a hatched step and a “Needs confirmation” tag. Forced colors: steps and the icon tile switch to Canvas/CanvasText outlines, steps with a Highlight fill. Reduced motion (OS or data-motion="reduce"): no pulse, the tile and steps change without animating. Known limit: the acknowledgement follows the reading in the tab order, so when the reading sits above the controls a keyboard user reaches it with Shift+Tab (the assertive announcement says confirmation is needed, and submitting moves focus to it); put the reading after the controls, or use data-exposure-reading, when they are far apart.

이 페이지는 AI로 번역되었습니다. 번역 문제 신고