mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 15:59:02 +00:00
Merge origin/feat/goals-v2-architecture; reconcile beta→preview rename
Remote branch added a beta_gated_nav_item helper + 'Gating the main nav' docs section. Main concurrently renamed the beta-features gate to preview-features (concern, predicate, JSONB key, locale flash). Rename the new helper / partial local / pill marker to match preview naming and port the nav-gating docs into gating-a-preview-feature.md so the improvement survives the rename. Resolved conflicts: - db/schema.rb: take the later schema version (2026_05_19_100000). - docs/llm-guides/gating-a-beta-feature.md: accept main's deletion; port the 'Gating the main nav' section into the preview guide. Renames carried through to keep the gate wired end-to-end: - application_helper.rb: beta_gated_nav_item → preview_gated_nav_item; beta_features_enabled? → preview_features_enabled?; beta: → preview:. - _nav_item.html.erb: beta: local → preview: local; shared.beta i18n key → shared.preview. - application.html.erb: caller renamed to preview_gated_nav_item. - goals/index.html.erb: pill label uses shared.preview. - shared/en.yml: 'beta: Beta' → 'preview: Preview'. - goals_controller, goal_pledges_controller: require_beta_features! → require_preview_features!. - goals_controller_test, goal_pledges_controller_test: flip the preference key, flash matcher, and test names to 'preview'.
This commit is contained in:
@@ -22,31 +22,42 @@ export default class extends Controller {
|
||||
presetColors: Array,
|
||||
};
|
||||
|
||||
initialize() {
|
||||
this.pickerBtnTarget.addEventListener("click", () => {
|
||||
this.showPaletteSection();
|
||||
});
|
||||
|
||||
this.colorInputTarget.addEventListener("input", (e) => {
|
||||
this.picker.setColor(e.target.value);
|
||||
});
|
||||
|
||||
this.detailsTarget.addEventListener("toggle", (e) => {
|
||||
connect() {
|
||||
// Bound references stored on the instance so disconnect() can remove
|
||||
// them. Without this, every Turbo navigation that re-renders the
|
||||
// picker stacks another listener on the same node.
|
||||
this._onPickerBtnClick = () => this.showPaletteSection();
|
||||
this._onColorInputInput = (e) => this.picker?.setColor(e.target.value);
|
||||
this._onDetailsToggle = (e) => {
|
||||
if (!this.colorInputTarget.checkValidity()) {
|
||||
e.preventDefault();
|
||||
this.colorInputTarget.reportValidity();
|
||||
e.target.open = true;
|
||||
}
|
||||
this.updatePopupPosition()
|
||||
});
|
||||
this.updatePopupPosition();
|
||||
};
|
||||
|
||||
this.pickerBtnTarget.addEventListener("click", this._onPickerBtnClick);
|
||||
this.colorInputTarget.addEventListener("input", this._onColorInputInput);
|
||||
this.detailsTarget.addEventListener("toggle", this._onDetailsToggle);
|
||||
document.addEventListener("mousedown", this.handleOutsideClick);
|
||||
|
||||
this.selectedIcon = null;
|
||||
|
||||
if (!this.presetColorsValue.includes(this.colorInputTarget.value)) {
|
||||
this.colorPickerRadioBtnTarget.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", this.handleOutsideClick);
|
||||
disconnect() {
|
||||
this.pickerBtnTarget.removeEventListener("click", this._onPickerBtnClick);
|
||||
this.colorInputTarget.removeEventListener("input", this._onColorInputInput);
|
||||
this.detailsTarget.removeEventListener("toggle", this._onDetailsToggle);
|
||||
document.removeEventListener("mousedown", this.handleOutsideClick);
|
||||
if (this.picker) {
|
||||
this.picker.destroyAndRemove();
|
||||
this.picker = null;
|
||||
}
|
||||
}
|
||||
|
||||
initPicker() {
|
||||
|
||||
@@ -433,7 +433,12 @@ export default class extends Controller {
|
||||
.attr("pointer-events", "none")
|
||||
.style("display", "none");
|
||||
|
||||
if (root.style.position !== "absolute") root.style.position = "relative";
|
||||
// Only promote root to a positioned ancestor when it currently has no
|
||||
// positioning context. Inline checks against `root.style.position`
|
||||
// miss positions set via CSS (the inline style is empty), so we'd
|
||||
// clobber a stylesheet `position: fixed/sticky/absolute` with our
|
||||
// own `relative`. Read the computed style instead.
|
||||
if (getComputedStyle(root).position === "static") root.style.position = "relative";
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.style.cssText = "position:absolute;pointer-events:none;display:none;background:var(--color-gray-900);color:var(--color-white);font-size:12px;line-height:1.35;padding:6px 8px;border-radius:6px;white-space:nowrap;z-index:5;box-shadow:0 2px 8px rgba(0,0,0,0.15);";
|
||||
root.appendChild(tooltip);
|
||||
|
||||
@@ -32,6 +32,10 @@ export default class extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
clearTimeout(this._urlSyncTimer);
|
||||
}
|
||||
|
||||
filter() {
|
||||
const query = this.hasInputTarget
|
||||
? this.inputTarget.value.toLocaleLowerCase().trim()
|
||||
@@ -60,7 +64,16 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
this.updateEmptyState(visible, query, active);
|
||||
this.#syncUrl();
|
||||
this.#scheduleUrlSync();
|
||||
}
|
||||
|
||||
// Debounced wrapper. Firing replaceState on every keystroke is wasteful
|
||||
// and produced visible jank on slow CPUs; deferring 200 ms collapses a
|
||||
// typing burst into a single URL update without losing back-button
|
||||
// fidelity (replaceState doesn't create history entries anyway).
|
||||
#scheduleUrlSync() {
|
||||
clearTimeout(this._urlSyncTimer);
|
||||
this._urlSyncTimer = setTimeout(() => this.#syncUrl(), 200);
|
||||
}
|
||||
|
||||
#hydrateFromUrl() {
|
||||
|
||||
Reference in New Issue
Block a user