diff --git a/.changeset/css-prefix.md b/.changeset/css-prefix.md new file mode 100644 index 0000000..7381940 --- /dev/null +++ b/.changeset/css-prefix.md @@ -0,0 +1,5 @@ +--- +"@maptoolkit/elevation-profile": minor +--- + +Prefix all CSS classes, ids and custom properties with `maptoolkit-elevation-profile-`, so they don't collide with the styles of the host page. Custom CSS has to use the new names, e.g. `.maptoolkit-elevation-profile-line` instead of `.line` and `--maptoolkit-elevation-profile-text` instead of `--text`. diff --git a/README.md b/README.md index 97b946d..9cea92e 100644 --- a/README.md +++ b/README.md @@ -163,38 +163,40 @@ Distances are in meters, or feet with `units: "imperial"`, so values from events ## Styling -The constructor adds the class `maptoolkit-elevation-profile` to the container; all styles in `style.css` are scoped under it. Colors and font are CSS custom properties: +The constructor adds the class `maptoolkit-elevation-profile` to the container; all styles in `style.css` are scoped under it. All classes, ids and CSS custom properties are prefixed with `maptoolkit-elevation-profile-`, so they don't collide with the styles of the page. Colors and font are custom properties: ```css .maptoolkit-elevation-profile { - --text: #333; - --muted: #666; - --grid: #aaa; - --fill: #002361; - --line: #002361; - --accent: #fa5538; - --badge-bg: none; - --font-family: "Mulish", "Helvetica Neue", Arial, sans-serif; + --maptoolkit-elevation-profile-text: #333; + --maptoolkit-elevation-profile-muted: #666; + --maptoolkit-elevation-profile-grid: #aaa; + --maptoolkit-elevation-profile-fill: #002361; + --maptoolkit-elevation-profile-line: #002361; + --maptoolkit-elevation-profile-accent: #fa5538; + --maptoolkit-elevation-profile-badge-bg: none; + --maptoolkit-elevation-profile-font-family: "Mulish", "Helvetica Neue", Arial, sans-serif; } ``` -| Class | Element | -| -------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `.line`, `.area` | Profile line and the area below it. | -| `.grid-line-x`, `.grid-line-y`, `.axis-line-x`, `.axis-line-y` | Grid lines; the first one of each direction is also the axis. | -| `.axis-label-x`, `.axis-label-y` | Axis labels. | -| `.marker-start`, `.marker-end`, `.marker-max` | Marker groups, each with `.marker` and `.marker-label`. | -| `.hover-line`, `.hover-dot` | Hover point, following the mouse. | -| `.hover-badge` | Badge with `.hover-badge-bg`, `.hover-badge-text` and `.hover-badge-dot`. | -| `.selection-rect` | Drag selection. | -| `.section-` | One section bar. | -| `.section-value.section--` | A bar segment and the matching badge dot. | +Classes, shown without the `maptoolkit-elevation-profile-` prefix: -Section colors are set per value via `--section-color`; values without a rule use the fallback `#9e9e9e`. Characters outside `[A-Za-z0-9_-]` in ids and values become `-` in class names. +| Class | Element | +| ---------------------------------------------------------- | ---------------------------------------------------------------------- | +| `line`, `area` | Profile line and the area below it. | +| `grid-line-x`, `grid-line-y`, `axis-line-x`, `axis-line-y` | Grid lines; the first one of each direction is also the axis. | +| `axis-label-x`, `axis-label-y` | Axis labels. | +| `marker-start`, `marker-end`, `marker-max` | Marker groups, each with `marker` and `marker-label`. | +| `hover-line`, `hover-dot` | Hover point, following the mouse. | +| `hover-badge` | Badge with `hover-badge-bg`, `hover-badge-text` and `hover-badge-dot`. | +| `selection-rect` | Drag selection. | +| `section-` | One section bar. | +| `section-value`, `section--` | A bar segment and the matching badge dot. | + +Section colors are set per value via `--maptoolkit-elevation-profile-section-color`; values without a rule use the fallback `#9e9e9e`. Characters outside `[A-Za-z0-9_-]` in ids and values become `-` in class names. ```css -.maptoolkit-elevation-profile .section-surface-gravel { - --section-color: #b9a89b; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-surface-gravel { + --maptoolkit-elevation-profile-section-color: #b9a89b; } ``` diff --git a/src/ElevationProfile.ts b/src/ElevationProfile.ts index d63119b..3ee0e2a 100644 --- a/src/ElevationProfile.ts +++ b/src/ElevationProfile.ts @@ -10,7 +10,9 @@ // section below the plot, from/to as 0..1 of the line length. For a MultiLineString // one such array per part ([[...], [...]]), from/to relative to that part; sections // with the same id are joined into one bar. -// Styling: .section- (bar) and .section-- (segment and badge dot). +// Styling: all classes, ids and CSS custom properties are prefixed with maptoolkit-elevation-profile-, +// e.g. .maptoolkit-elevation-profile-section- (bar) and +// .maptoolkit-elevation-profile-section-- (segment and badge dot). // Badge labels: options.locale["."], else the raw value. // // Events (profile.on/off/once), all distances and elevations in meters (feet for imperial): @@ -60,6 +62,11 @@ export const defaultElevationProfileOptions: ElevationProfileOptions = { type Position = number[]; +// Prefix of all CSS classes and ids, so styles of the host page don't collide with them. +const PREFIX = "maptoolkit-elevation-profile"; +// Prefixed class names, e.g. cls("line", "area") -> "maptoolkit-elevation-profile-line maptoolkit-elevation-profile-area". +const cls = (...names: string[]) => names.map((name) => `${PREFIX}-${name}`).join(" "); + export interface SectionValueInput { from?: number; to?: number; @@ -234,14 +241,14 @@ export class ElevationProfile { if (!element) throw new Error(`Element #${container} nicht gefunden.`); this.container = element; // Scopes the styles in style.css. - this.container.classList.add("maptoolkit-elevation-profile"); + this.container.classList.add(PREFIX); const { units } = { ...defaultElevationProfileOptions, ...options }; const locale = { ...defaultElevationProfileOptions.locale, ...options.locale }; const unit = ElevationProfile.UNIT_SYSTEMS[units]; if (!unit) throw new Error(`Unbekanntes Masssystem "${units}".`); this.unit = unit; this.locale = locale; - this.clipId = `elevationClip-${++ElevationProfile.instanceCount}`; + this.clipId = `${PREFIX}-clip-${++ElevationProfile.instanceCount}`; } // Renders a 3D GeoJSON Feature, replacing previous content. @@ -322,9 +329,9 @@ export class ElevationProfile { return String(str).replace(/[^A-Za-z0-9_-]/g, "-"); } - // Classes of a bar segment and its badge dot. Colors come from .section-- in the CSS. + // Classes of a bar segment and its badge dot. Colors come from ...-section-- in the CSS. private static sectionValueClass(id: string, value: string): string { - return `section-value section-${ElevationProfile.cssName(id)}-${ElevationProfile.cssName(value)}`; + return cls("section-value", `section-${ElevationProfile.cssName(id)}-${ElevationProfile.cssName(value)}`); } // "Nice numbers" axis ticks (Heckbert). @@ -481,7 +488,7 @@ export class ElevationProfile { const linePath = `M${linePoints.join(" L")}`; // Start, end and highest point with elevation label, each in a - // for separate styling. + // for separate styling. // The highest point is skipped if it is too close to start or end, so the labels don't overlap. const lastIdx = samples.length - 1; const maxIdx = samples.indexOf(elevMax); @@ -504,9 +511,9 @@ export class ElevationProfile { const ly = above ? my - markerR - markerLabelGap : my + markerR + markerLabelGap; // Keep labels near the edges inside the plot. const anchor = mx < margin.left + 30 ? "start" : mx > width - margin.right - 30 ? "end" : "middle"; - return ` - - ${this.formatElevation(samples[i])} + return ` + + ${this.formatElevation(samples[i])} `; }) .join("\n"); @@ -549,7 +556,7 @@ export class ElevationProfile { .map((section, i) => { const barY = margin.top + plotH + sectionBar.gap + i * (sectionBar.height + sectionBar.gap); const rects = barRects(section.runs, barY, (value) => ElevationProfile.sectionValueClass(section.id, value)); - return `\n${rects}\n `; + return `\n${rects}\n `; }) .join("\n"); @@ -558,8 +565,8 @@ export class ElevationProfile { .map((t, i) => { const yy = y(t / elevFactor).toFixed(1); // The lowest line is the x axis. - return ` - ${Math.round(t)} ${unit.elevation.unit}`; + return ` + ${Math.round(t)} ${unit.elevation.unit}`; }) .join("\n"); @@ -568,8 +575,8 @@ export class ElevationProfile { .map((t, i) => { const xx = x(t / distFactor).toFixed(1); // The line at 0 is the y axis. - return ` - ${t} ${unit.distance.unit}`; + return ` + ${t} ${unit.distance.unit}`; }) .join("\n"); @@ -580,7 +587,7 @@ export class ElevationProfile { ? `` : ``, ).join(""); - const badgeDots = Array.from({ length: badgeLineCount }, () => ``).join("\n "); + const badgeDots = Array.from({ length: badgeLineCount }, () => ``).join("\n "); const svg = ` @@ -591,18 +598,18 @@ export class ElevationProfile { ${gridLinesY} ${gridLinesX} - - + + ${sectionBars} ${markers} - - - - - - + + + + + + - ${badgeTspans} + ${badgeTspans} ${badgeDots} `; @@ -672,15 +679,15 @@ export class ElevationProfile { private setupInteraction(svgEl: SVGSVGElement, meta: SvgMeta): Interaction { const { margin, plotW, plotH, safeDistance, distances, samples, coordinates, yTicks, sections, badge } = meta; - const capture = svgEl.querySelector(".hover-capture")!; - const hoverLine = svgEl.querySelector(".hover-line")!; - const hoverDot = svgEl.querySelector(".hover-dot")!; - const selectionRect = svgEl.querySelector(".selection-rect")!; - const badgeEl = svgEl.querySelector(".hover-badge")!; - const badgeBg = badgeEl.querySelector(".hover-badge-bg")!; - const badgeText = badgeEl.querySelector(".hover-badge-text")!; + const capture = svgEl.querySelector(`.${cls("hover-capture")}`)!; + const hoverLine = svgEl.querySelector(`.${cls("hover-line")}`)!; + const hoverDot = svgEl.querySelector(`.${cls("hover-dot")}`)!; + const selectionRect = svgEl.querySelector(`.${cls("selection-rect")}`)!; + const badgeEl = svgEl.querySelector(`.${cls("hover-badge")}`)!; + const badgeBg = badgeEl.querySelector(`.${cls("hover-badge-bg")}`)!; + const badgeText = badgeEl.querySelector(`.${cls("hover-badge-text")}`)!; const badgeLines = Array.from(badgeText.querySelectorAll("tspan")); - const badgeDots = Array.from(badgeEl.querySelectorAll(".hover-badge-dot")); + const badgeDots = Array.from(badgeEl.querySelectorAll(`.${cls("hover-badge-dot")}`)); const nearestSampleIndex = ElevationProfile.nearestSampleIndex; // Right-aligned lines (one tspan each), each with an optional colored dot after the text. @@ -694,7 +701,7 @@ export class ElevationProfile { tspan.setAttribute("x", String(line?.dotClass ? textRight - 2 * badge.dotRadius - badge.dotGap : textRight)); const dot = badgeDots[i]; if (line?.dotClass) { - dot.setAttribute("class", `hover-badge-dot ${line.dotClass}`); + dot.setAttribute("class", `${cls("hover-badge-dot")} ${line.dotClass}`); dot.setAttribute("cx", String(textRight - badge.dotRadius)); dot.setAttribute("cy", String(badge.top + badge.padY + badge.fontSize + i * badge.lineHeight - badge.fontSize * 0.35)); } diff --git a/src/style.css b/src/style.css index 82fa7b8..3290eea 100644 --- a/src/style.css +++ b/src/style.css @@ -1,60 +1,60 @@ /* Styles for ElevationProfile (ElevationProfile.ts). */ .maptoolkit-elevation-profile { - --text: #333; - --muted: #666; - --grid: #aaa; - --fill: #002361; - --line: #002361; - --accent: #fa5538; - --badge-bg: none; - --font-family: "Mulish", "Helvetica Neue", Arial, sans-serif; + --maptoolkit-elevation-profile-text: #333; + --maptoolkit-elevation-profile-muted: #666; + --maptoolkit-elevation-profile-grid: #aaa; + --maptoolkit-elevation-profile-fill: #002361; + --maptoolkit-elevation-profile-line: #002361; + --maptoolkit-elevation-profile-accent: #fa5538; + --maptoolkit-elevation-profile-badge-bg: none; + --maptoolkit-elevation-profile-font-family: "Mulish", "Helvetica Neue", Arial, sans-serif; } .maptoolkit-elevation-profile svg { display: block; width: 100%; height: auto; - font-family: var(--font-family); + font-family: var(--maptoolkit-elevation-profile-font-family); user-select: none; } -.maptoolkit-elevation-profile .grid-line-x { +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-grid-line-x { stroke: none; } -.maptoolkit-elevation-profile .grid-line-y { - stroke: var(--grid); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-grid-line-y { + stroke: var(--maptoolkit-elevation-profile-grid); stroke-width: 1; stroke-opacity: 1; stroke-dasharray: 2 20; } -.maptoolkit-elevation-profile .axis-label { - fill: var(--text); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-axis-label { + fill: var(--maptoolkit-elevation-profile-text); font-size: 18px; } -.maptoolkit-elevation-profile .area { - fill: var(--fill); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-area { + fill: var(--maptoolkit-elevation-profile-fill); fill-opacity: 0.1; } -.maptoolkit-elevation-profile .line { +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-line { fill: none; - stroke: var(--line); + stroke: var(--maptoolkit-elevation-profile-line); stroke-width: 5; stroke: #002361; stroke-opacity: 0.8; stroke-linejoin: round; stroke-linecap: round; } -.maptoolkit-elevation-profile .marker { +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-marker { fill: Canvas; - stroke: var(--line); + stroke: var(--maptoolkit-elevation-profile-line); stroke-width: 2; } -.maptoolkit-elevation-profile .marker-start, -.maptoolkit-elevation-profile .marker-end, -.maptoolkit-elevation-profile .marker-max { +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-marker-start, +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-marker-end, +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-marker-max { display: none; } /* font-size must match markerFontSize in ElevationProfile.ts. */ -.maptoolkit-elevation-profile .marker-label { - fill: var(--text); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-marker-label { + fill: var(--maptoolkit-elevation-profile-text); font-size: 18px; /* Halo so the label stays readable over the line and grid. */ stroke: Canvas; @@ -62,84 +62,84 @@ stroke-width: 4; stroke-linejoin: round; } -.maptoolkit-elevation-profile .hover-line { - stroke: var(--muted); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-hover-line { + stroke: var(--maptoolkit-elevation-profile-muted); stroke-width: 1; stroke-dasharray: 3 3; display: none; } -.maptoolkit-elevation-profile .selection-rect { - fill: var(--line); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-selection-rect { + fill: var(--maptoolkit-elevation-profile-line); fill-opacity: 0.15; - stroke: var(--line); + stroke: var(--maptoolkit-elevation-profile-line); stroke-width: 1; display: none; } -.maptoolkit-elevation-profile .hover-dot { - fill: var(--accent); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-hover-dot { + fill: var(--maptoolkit-elevation-profile-accent); r: 8; stroke: #fff; stroke-width: 2; display: none; - filter: drop-shadow(0 0 3px var(--accent)); + filter: drop-shadow(0 0 3px var(--maptoolkit-elevation-profile-accent)); } -.maptoolkit-elevation-profile .hover-badge { +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-hover-badge { pointer-events: none; display: none; } -.maptoolkit-elevation-profile .hover-badge-bg { - fill: var(--badge-bg); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-hover-badge-bg { + fill: var(--maptoolkit-elevation-profile-badge-bg); stroke-width: 0; vector-effect: non-scaling-stroke; } /* font-size must match badge.fontSize in ElevationProfile.ts. */ -.maptoolkit-elevation-profile .hover-badge-text { - fill: var(--text); +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-hover-badge-text { + fill: var(--maptoolkit-elevation-profile-text); font-size: 20px; stroke: rgba(255, 255, 255, 0.8); paint-order: stroke; stroke-width: 4; stroke-linejoin: round; } -.maptoolkit-elevation-profile .hover-badge-dot { +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-hover-badge-dot { stroke: rgba(255, 255, 255, 0.8); r: 9; stroke-width: 2; } -/* Section bars and badge dots. Colors per value via --section-color on - .section--, values without a rule use the fallback. - A whole bar can be targeted with .section-. */ -.maptoolkit-elevation-profile .section-value { - fill: var(--section-color, #9e9e9e); +/* Section bars and badge dots. Colors per value via --maptoolkit-elevation-profile-section-color on + .maptoolkit-elevation-profile-section--, values without a rule use the fallback. + A whole bar can be targeted with .maptoolkit-elevation-profile-section-. */ +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-value { + fill: var(--maptoolkit-elevation-profile-section-color, #9e9e9e); } /* Default surface colors. */ -.maptoolkit-elevation-profile .section-surface-asphalt { - --section-color: #2f4858; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-surface-asphalt { + --maptoolkit-elevation-profile-section-color: #2f4858; } -.maptoolkit-elevation-profile .section-surface-paved { - --section-color: #847467; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-surface-paved { + --maptoolkit-elevation-profile-section-color: #847467; } -.maptoolkit-elevation-profile .section-surface-unpaved { - --section-color: #b9a89b; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-surface-unpaved { + --maptoolkit-elevation-profile-section-color: #b9a89b; } -.maptoolkit-elevation-profile .section-surface-natural { - --section-color: #643800; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-surface-natural { + --maptoolkit-elevation-profile-section-color: #643800; } -.maptoolkit-elevation-profile .section-surface-alpine { - --section-color: #208257; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-surface-alpine { + --maptoolkit-elevation-profile-section-color: #208257; } -.maptoolkit-elevation-profile .section-surface-other { - --section-color: #816f95; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-surface-other { + --maptoolkit-elevation-profile-section-color: #816f95; } -.maptoolkit-elevation-profile .section-highway-road { - --section-color: #5c5c5c; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-highway-road { + --maptoolkit-elevation-profile-section-color: #5c5c5c; } -.maptoolkit-elevation-profile .section-highway-street { - --section-color: #8a7f75; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-highway-street { + --maptoolkit-elevation-profile-section-color: #8a7f75; } -.maptoolkit-elevation-profile .section-highway-path { - --section-color: #c29b62; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-highway-path { + --maptoolkit-elevation-profile-section-color: #c29b62; } -.maptoolkit-elevation-profile .section-highway-pedestrian { - --section-color: #6f8fae; +.maptoolkit-elevation-profile .maptoolkit-elevation-profile-section-highway-pedestrian { + --maptoolkit-elevation-profile-section-color: #6f8fae; } diff --git a/test/ElevationProfile.test.ts b/test/ElevationProfile.test.ts index 1951f96..4e36b1d 100644 --- a/test/ElevationProfile.test.ts +++ b/test/ElevationProfile.test.ts @@ -74,7 +74,7 @@ function create(options: Partial = {}, feature = lineFe const $ = (selector: string) => document.querySelector(`#profile ${selector}`)!; const $$ = (selector: string) => Array.from(document.querySelectorAll(`#profile ${selector}`)); const texts = (selector: string) => $$(selector).map((el) => el.textContent); -const badgeLines = () => texts(".hover-badge-text tspan"); +const badgeLines = () => texts(".maptoolkit-elevation-profile-hover-badge-text tspan"); // clientX for a distance in meters on a 1000 m profile. const clientX = (meter: number) => PLOT_LEFT + (meter / 1000) * PLOT_WIDTH; @@ -85,15 +85,15 @@ function mouse(target: EventTarget, type: string, meter: number, clientY = 50): return evt; } -const hover = (meter: number) => mouse($(".hover-capture"), "mousemove", meter); +const hover = (meter: number) => mouse($(".maptoolkit-elevation-profile-hover-capture"), "mousemove", meter); function click(meter: number) { - mouse($(".hover-capture"), "mousedown", meter); + mouse($(".maptoolkit-elevation-profile-hover-capture"), "mousedown", meter); mouse(window, "mouseup", meter); } function drag(from: number, to: number) { - mouse($(".hover-capture"), "mousedown", from); + mouse($(".maptoolkit-elevation-profile-hover-capture"), "mousedown", from); mouse(window, "mousemove", to); mouse(window, "mouseup", to); } @@ -158,18 +158,18 @@ describe("not rendered container", () => { const profile = new ElevationProfile(element); profile.render(lineFeature()); profile.showHoverAt(300); - expect(element.querySelector(".hover-line")!.style.display).not.toBe("block"); + expect(element.querySelector(".maptoolkit-elevation-profile-hover-line")!.style.display).not.toBe("block"); expect(profile.setSelection(250, 750)).toBeNull(); - expect(element.querySelector(".selection-rect")!.style.display).not.toBe("block"); + expect(element.querySelector(".maptoolkit-elevation-profile-selection-rect")!.style.display).not.toBe("block"); }); it("ignores showHoverAt and setSelection inside display: none", () => { document.body.innerHTML = '
'; const profile = create(); profile.showHoverAt(300); - expect($(".hover-line").style.display).not.toBe("block"); + expect($(".maptoolkit-elevation-profile-hover-line").style.display).not.toBe("block"); expect(profile.setSelection(250, 750)).toBeNull(); - expect($(".selection-rect").style.display).not.toBe("block"); + expect($(".maptoolkit-elevation-profile-selection-rect").style.display).not.toBe("block"); }); it("keeps a selection made while visible and allows clearing it while hidden", () => { @@ -177,9 +177,9 @@ describe("not rendered container", () => { profile.setSelection(250, 750); const element = document.getElementById("profile")!; element.style.display = "none"; - expect($(".selection-rect").style.display).toBe("block"); + expect($(".maptoolkit-elevation-profile-selection-rect").style.display).toBe("block"); profile.clearSelection(); - expect($(".selection-rect").style.display).toBe("none"); + expect($(".maptoolkit-elevation-profile-selection-rect").style.display).toBe("none"); }); }); @@ -193,29 +193,29 @@ describe("render", () => { it("renders metric axis labels", () => { create(); - expect(texts(".axis-label-y").every((t) => / m$/.test(t!))).toBe(true); - expect(texts(".axis-label-x").every((t) => / km$/.test(t!))).toBe(true); - expect(texts(".axis-label-x")[0]).toBe("0 km"); + expect(texts(".maptoolkit-elevation-profile-axis-label-y").every((t) => / m$/.test(t!))).toBe(true); + expect(texts(".maptoolkit-elevation-profile-axis-label-x").every((t) => / km$/.test(t!))).toBe(true); + expect(texts(".maptoolkit-elevation-profile-axis-label-x")[0]).toBe("0 km"); }); it("renders imperial axis labels", () => { create({ units: "imperial" }); - expect(texts(".axis-label-y").every((t) => / ft$/.test(t!))).toBe(true); - expect(texts(".axis-label-x").every((t) => / mi$/.test(t!))).toBe(true); + expect(texts(".maptoolkit-elevation-profile-axis-label-y").every((t) => / ft$/.test(t!))).toBe(true); + expect(texts(".maptoolkit-elevation-profile-axis-label-x").every((t) => / mi$/.test(t!))).toBe(true); }); it("renders start, end and max markers", () => { create(); - expect(texts(".marker-start .marker-label")).toEqual(["100 m"]); - expect(texts(".marker-end .marker-label")).toEqual(["160 m"]); - expect(texts(".marker-max .marker-label")).toEqual(["180 m"]); + expect(texts(".maptoolkit-elevation-profile-marker-start .maptoolkit-elevation-profile-marker-label")).toEqual(["100 m"]); + expect(texts(".maptoolkit-elevation-profile-marker-end .maptoolkit-elevation-profile-marker-label")).toEqual(["160 m"]); + expect(texts(".maptoolkit-elevation-profile-marker-max .maptoolkit-elevation-profile-marker-label")).toEqual(["180 m"]); }); it("skips the max marker next to start or end", () => { const coords = coordinates.map(([lng, lat], i) => [lng, lat, i === 4 ? 200 : 100]); create({}, { geometry: { type: "LineString", coordinates: coords }, properties: { length: 1000 } }); - expect($$(".marker-max")).toHaveLength(0); - expect(texts(".marker-end .marker-label")).toEqual(["200 m"]); + expect($$(".maptoolkit-elevation-profile-marker-max")).toHaveLength(0); + expect(texts(".maptoolkit-elevation-profile-marker-end .maptoolkit-elevation-profile-marker-label")).toEqual(["200 m"]); }); it("replaces previous content", () => { @@ -224,17 +224,25 @@ describe("render", () => { expect($$("svg")).toHaveLength(1); }); - it("uses a unique clip path per instance", () => { + it("uses a unique, prefixed clip path per instance", () => { document.body.innerHTML = '
'; new ElevationProfile("a").render(lineFeature()); new ElevationProfile("b").render(lineFeature()); const ids = Array.from(document.querySelectorAll("clipPath")).map((el) => el.id); expect(new Set(ids).size).toBe(2); + ids.forEach((id) => expect(id).toMatch(/^maptoolkit-elevation-profile-clip-\d+$/)); + }); + + it("prefixes all classes", () => { + create({}, lineFeature({ sections: [{ id: "surface", values: [{ from: 0, to: 1, value: "asphalt" }] }] })); + const classes = $$("[class]").flatMap((el) => el.getAttribute("class")!.split(" ")); + expect(classes.length).toBeGreaterThan(0); + classes.forEach((name) => expect(name).toMatch(/^maptoolkit-elevation-profile-/)); }); }); describe("sections", () => { - const segmentClasses = (id: string) => $$(`.section-${id} rect`).map((el) => el.getAttribute("class")); + const segmentClasses = (id: string) => $$(`.maptoolkit-elevation-profile-section-${id} rect`).map((el) => el.getAttribute("class")); it("renders one bar per section id", () => { create( @@ -246,9 +254,9 @@ describe("sections", () => { ], }), ); - expect($$(".section")).toHaveLength(2); - expect(segmentClasses("surface")).toEqual(["section-value section-surface-asphalt"]); - expect(segmentClasses("highway")).toEqual(["section-value section-highway-path"]); + expect($$(".maptoolkit-elevation-profile-section")).toHaveLength(2); + expect(segmentClasses("surface")).toEqual(["maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-asphalt"]); + expect(segmentClasses("highway")).toEqual(["maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-highway-path"]); }); it("fills gaps with other", () => { @@ -267,9 +275,9 @@ describe("sections", () => { }), ); expect(segmentClasses("surface")).toEqual([ - "section-value section-surface-asphalt", - "section-value section-surface-other", - "section-value section-surface-asphalt", + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-asphalt", + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-other", + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-asphalt", ]); }); @@ -289,13 +297,16 @@ describe("sections", () => { ], }), ); - expect(segmentClasses("surface")).toEqual(["section-value section-surface-asphalt", "section-value section-surface-gravel"]); + expect(segmentClasses("surface")).toEqual([ + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-asphalt", + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-gravel", + ]); }); it("maps ids and values to CSS-safe class names", () => { create({}, lineFeature({ sections: [{ id: "road type", values: [{ from: 0, to: 1, value: "a/b" }] }] })); - expect($$(".section-road-type")).toHaveLength(1); - expect(segmentClasses("road-type")).toEqual(["section-value section-road-type-a-b"]); + expect($$(".maptoolkit-elevation-profile-section-road-type")).toHaveLength(1); + expect(segmentClasses("road-type")).toEqual(["maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-road-type-a-b"]); }); it("joins MultiLineString parts by id, relative to each part", () => { @@ -325,11 +336,11 @@ describe("sections", () => { }, }, ); - expect($$(".section")).toHaveLength(1); + expect($$(".maptoolkit-elevation-profile-section")).toHaveLength(1); expect(segmentClasses("surface")).toEqual([ - "section-value section-surface-asphalt", - "section-value section-surface-other", - "section-value section-surface-gravel", + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-asphalt", + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-other", + "maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-gravel", ]); const values: string[] = []; profile.on("hover", (e) => values.push(e.sections.surface)); @@ -369,10 +380,12 @@ describe("hover badge", () => { it("shows a dot with section classes for section lines only", () => { const profile = create({}, sectioned()); profile.showHoverAt(300); - const dots = $$(".hover-badge-dot"); + const dots = $$(".maptoolkit-elevation-profile-hover-badge-dot"); expect(dots[0].style.display).toBe("none"); expect(dots[1].style.display).toBe("block"); - expect(dots[1].getAttribute("class")).toBe("hover-badge-dot section-value section-surface-asphalt"); + expect(dots[1].getAttribute("class")).toBe( + "maptoolkit-elevation-profile-hover-badge-dot maptoolkit-elevation-profile-section-value maptoolkit-elevation-profile-section-surface-asphalt", + ); }); it("shows ascent and descent for a selection", () => { @@ -402,11 +415,11 @@ describe("events", () => { it("fires hoverend once", () => { const profile = create(); const spies = spyAll(profile); - mouse($(".hover-capture"), "mouseleave", 0); + mouse($(".maptoolkit-elevation-profile-hover-capture"), "mouseleave", 0); expect(spies.hoverend).not.toHaveBeenCalled(); hover(300); - mouse($(".hover-capture"), "mouseleave", 0); - mouse($(".hover-capture"), "mouseleave", 0); + mouse($(".maptoolkit-elevation-profile-hover-capture"), "mouseleave", 0); + mouse($(".maptoolkit-elevation-profile-hover-capture"), "mouseleave", 0); expect(spies.hoverend).toHaveBeenCalledTimes(1); }); @@ -446,7 +459,7 @@ describe("events", () => { it("treats a move of up to 3 px as a click", () => { const profile = create(); const spies = spyAll(profile); - mouse($(".hover-capture"), "mousedown", 500); + mouse($(".maptoolkit-elevation-profile-hover-capture"), "mousedown", 500); window.dispatchEvent(new MouseEvent("mousemove", { clientX: clientX(500) + 2, clientY: 51 })); mouse(window, "mouseup", 500); expect(spies.selection).not.toHaveBeenCalled(); @@ -505,7 +518,7 @@ describe("methods", () => { it("setSelection returns ascent and descent and shows the selection", () => { const profile = create(); expect(profile.setSelection(750, 250)).toEqual({ ascent: 60, descent: 30 }); - expect($(".selection-rect").style.display).toBe("block"); + expect($(".maptoolkit-elevation-profile-selection-rect").style.display).toBe("block"); }); it("setSelection clamps to the profile", () => { @@ -516,18 +529,18 @@ describe("methods", () => { it("showHoverAt shows the hover point, hideHover hides it", () => { const profile = create(); profile.showHoverAt(300); - expect($(".hover-line").style.display).toBe("block"); - expect($(".hover-badge").style.display).toBe("block"); + expect($(".maptoolkit-elevation-profile-hover-line").style.display).toBe("block"); + expect($(".maptoolkit-elevation-profile-hover-badge").style.display).toBe("block"); profile.hideHover(); - expect($(".hover-line").style.display).toBe("none"); - expect($(".hover-badge").style.display).toBe("none"); + expect($(".maptoolkit-elevation-profile-hover-line").style.display).toBe("none"); + expect($(".maptoolkit-elevation-profile-hover-badge").style.display).toBe("none"); }); it("showHoverAt is ignored while a selection exists", () => { const profile = create(); profile.setSelection(250, 750); profile.showHoverAt(300); - expect($(".hover-line").style.display).toBe("none"); + expect($(".maptoolkit-elevation-profile-hover-line").style.display).toBe("none"); expect(badgeLines()).toEqual(["+ 60 m", "- 30 m"]); }); @@ -535,7 +548,7 @@ describe("methods", () => { const profile = create(); profile.setSelection(250, 750); profile.clearSelection(); - expect($(".selection-rect").style.display).toBe("none"); + expect($(".maptoolkit-elevation-profile-selection-rect").style.display).toBe("none"); const spies = spyAll(profile); click(500); expect(spies.click).toHaveBeenCalledTimes(1); @@ -610,6 +623,6 @@ describe("imperial", () => { it("formats marker labels in feet", () => { create({ units: "imperial" }); - expect(texts(".marker-start .marker-label")).toEqual(["328 ft"]); + expect(texts(".maptoolkit-elevation-profile-marker-start .maptoolkit-elevation-profile-marker-label")).toEqual(["328 ft"]); }); });