mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
+Dynamic textarea sizing
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -117,9 +117,7 @@ type AlmanachResult struct {
|
||||
<div class="flex flex-row justify-between">
|
||||
<label for="preferred_title" class="inputlabel">Kurztitel</label>
|
||||
</div>
|
||||
<textarea name="preferred_title" id="preferred_title" class="inputinput no-enter" placeholder="" required autocomplete="off" rows="1">
|
||||
{{- $model.result.Entry.PreferredTitle -}}
|
||||
</textarea>
|
||||
<textarea name="preferred_title" id="preferred_title" class="inputinput no-enter" placeholder="" required autocomplete="off" rows="1">{{- $model.result.Entry.PreferredTitle -}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
@@ -142,9 +140,7 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea name="title" id="title" class="inputinput no-enter" placeholder="" autocomplete="off" rows="1">
|
||||
{{- $model.result.Entry.TitleStmt -}}
|
||||
</textarea>
|
||||
<textarea name="title" id="title" class="inputinput no-enter" placeholder="" autocomplete="off" rows="1">{{- $model.result.Entry.TitleStmt -}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 inputwrapper {{ if eq $model.result.Entry.ParallelTitle "" }}hidden{{ end }}" data-dm-target="titles">
|
||||
@@ -158,9 +154,7 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea name="paralleltitle" id="paralleltitle" class="inputinput" placeholder="" autocomplete="off">
|
||||
{{- $model.result.Entry.ParallelTitle -}}
|
||||
</textarea>
|
||||
<textarea name="paralleltitle" id="paralleltitle" class="inputinput" placeholder="" autocomplete="off" rows="1">{{- $model.result.Entry.ParallelTitle -}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 inputwrapper {{ if eq $model.result.Entry.SubtitleStmt "" }}hidden{{ end }}" data-dm-target="titles">
|
||||
@@ -173,9 +167,7 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea name="subtitle" id="subtitle" class="inputinput no-enter" placeholder="" autocomplete="off" rows="1">
|
||||
{{- $model.result.Entry.SubtitleStmt -}}
|
||||
</textarea>
|
||||
<textarea name="subtitle" id="subtitle" class="inputinput no-enter" placeholder="" autocomplete="off" rows="1">{{- $model.result.Entry.SubtitleStmt -}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 inputwrapper {{ if eq $model.result.Entry.VariantTitle "" }}hidden{{ end }}" data-dm-target="titles">
|
||||
@@ -188,9 +180,7 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea name="varianttitle" id="varianttitle" class="inputinput" placeholder="" autocomplete="off">
|
||||
{{- $model.result.Entry.VariantTitle -}}
|
||||
</textarea>
|
||||
<textarea name="varianttitle" id="varianttitle" class="inputinput" placeholder="" autocomplete="off" rows="1">{{- $model.result.Entry.VariantTitle -}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 inputwrapper {{ if eq $model.result.Entry.IncipitStmt "" }}hidden{{ end }}" data-dm-target="titles">
|
||||
@@ -203,9 +193,7 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea name="incipit" id="incipit" class="inputinput no-enter" placeholder="" autocomplete="off" rows="1">
|
||||
{{- $model.result.Entry.IncipitStmt -}}
|
||||
</textarea>
|
||||
<textarea name="incipit" id="incipit" class="inputinput no-enter" placeholder="" autocomplete="off" rows="1">{{- $model.result.Entry.IncipitStmt -}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="inputwrapper {{ if eq $model.result.Entry.ResponsibilityStmt "" }}hidden{{ end }}" data-dm-target="publication">
|
||||
@@ -272,7 +260,7 @@ type AlmanachResult struct {
|
||||
<!-- Annotationen -->
|
||||
<div class="inputwrapper">
|
||||
<label for="annotation" class="inputlabel">Annotationen</label>
|
||||
<textarea name="annotation" id="annotation" class="inputinput" placeholder="" autocomplete="off">{{- $model.result.Entry.Annotation -}}</textarea>
|
||||
<textarea name="annotation" id="annotation" class="inputinput" placeholder="" autocomplete="off" rows="1">{{- $model.result.Entry.Annotation -}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -613,7 +601,7 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea name="edit_comment" id="edit_comment" class="inputinput" placeholder="" autocomplete="off">{{- $model.result.Entry.Comment -}}</textarea>
|
||||
<textarea name="edit_comment" id="edit_comment" class="inputinput" placeholder="" autocomplete="off" rows="1">{{- $model.result.Entry.Comment -}}</textarea>
|
||||
</div>
|
||||
</div-manager>
|
||||
</div>
|
||||
|
||||
@@ -13,9 +13,12 @@ export class AlmanachEditPage extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this._initForm();
|
||||
this._initPlaces();
|
||||
this._initSaveHandling();
|
||||
// Small delay to ensure main.js has loaded
|
||||
setTimeout(() => {
|
||||
this._initForm();
|
||||
this._initPlaces();
|
||||
this._initSaveHandling();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -23,9 +26,13 @@ export class AlmanachEditPage extends HTMLElement {
|
||||
}
|
||||
|
||||
_initForm() {
|
||||
console.log("AlmanachEditPage: _initForm called");
|
||||
const form = this.querySelector("#changealmanachform");
|
||||
console.log("Form found:", !!form, "FormLoad exists:", typeof window.FormLoad === "function");
|
||||
if (form && typeof window.FormLoad === "function") {
|
||||
window.FormLoad(form);
|
||||
} else {
|
||||
console.error("Cannot initialize form - form or FormLoad missing");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,6 +443,15 @@ export class AlmanachEditPage extends HTMLElement {
|
||||
this._initForm();
|
||||
this._initPlaces();
|
||||
this._initSaveHandling();
|
||||
|
||||
// Resize all textareas after reload
|
||||
if (typeof window.TextareaAutoResize === "function") {
|
||||
setTimeout(() => {
|
||||
this.querySelectorAll("textarea").forEach((textarea) => {
|
||||
window.TextareaAutoResize(textarea);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -245,6 +245,19 @@ export class DivManager extends HTMLElement {
|
||||
this.renderMenu();
|
||||
this.renderButton();
|
||||
this.updateTargetVisibility();
|
||||
|
||||
// Resize any textareas in the newly shown element
|
||||
if (typeof window.TextareaAutoResize === "function") {
|
||||
const textareas = child.node.querySelectorAll("textarea");
|
||||
if (textareas.length > 0) {
|
||||
// Small delay to ensure element is visible before measuring
|
||||
setTimeout(() => {
|
||||
textareas.forEach((textarea) => {
|
||||
window.TextareaAutoResize(textarea);
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderMenu() {
|
||||
|
||||
@@ -21,6 +21,17 @@
|
||||
@apply block w-full focus:border-none focus:outline-none resize-y px-3 py-1;
|
||||
}
|
||||
|
||||
.dbform .inputwrapper textarea {
|
||||
/* Modern browsers: use CSS auto-sizing */
|
||||
field-sizing: content;
|
||||
/* Styling for all browsers */
|
||||
display: block;
|
||||
resize: none;
|
||||
max-height: 20rem;
|
||||
box-sizing: border-box;
|
||||
/* overflow will be managed by JS for browsers without field-sizing support */
|
||||
}
|
||||
|
||||
.inputlabeltext {
|
||||
@apply text-gray-700 font-bold;
|
||||
}
|
||||
|
||||
@@ -177,15 +177,63 @@ function HookupRBChange(target, action) {
|
||||
});
|
||||
}
|
||||
|
||||
// @param {HTMLTextAreaElement} textarea - The textarea element.
|
||||
// Check if browser supports field-sizing: content (Chrome 123+, Edge 123+)
|
||||
// Firefox, Safari don't support it yet as of 2024
|
||||
let browserSupportsFieldSizing = null;
|
||||
function supportsFieldSizing() {
|
||||
if (browserSupportsFieldSizing !== null) {
|
||||
return browserSupportsFieldSizing;
|
||||
}
|
||||
// Check if CSS.supports is available and test for field-sizing
|
||||
if (typeof CSS !== "undefined" && typeof CSS.supports === "function") {
|
||||
browserSupportsFieldSizing = CSS.supports("field-sizing", "content");
|
||||
} else {
|
||||
browserSupportsFieldSizing = false;
|
||||
}
|
||||
console.log("Browser supports field-sizing:", browserSupportsFieldSizing);
|
||||
return browserSupportsFieldSizing;
|
||||
}
|
||||
|
||||
// Simple textarea auto-resize function
|
||||
function TextareaAutoResize(textarea) {
|
||||
console.log("TextareaAutoResize called for:", textarea.name || textarea.id);
|
||||
|
||||
if (!(textarea instanceof HTMLTextAreaElement)) {
|
||||
console.warn("TextareaAutoResize: Provided element is not a textarea.");
|
||||
console.log("Not a textarea element");
|
||||
return;
|
||||
}
|
||||
|
||||
textarea.style.height = "auto";
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
// Skip if not visible
|
||||
if (textarea.offsetParent === null) {
|
||||
console.log("Textarea not visible");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove rows attribute
|
||||
textarea.removeAttribute("rows");
|
||||
|
||||
// Set overflow auto to allow scrolling if needed
|
||||
textarea.style.overflow = "auto";
|
||||
|
||||
// Special case: annotation textarea has 2 rows minimum
|
||||
const isAnnotation = textarea.name === "annotation";
|
||||
const minHeight = isAnnotation ? 76 : 38; // 2 rows vs 1 row
|
||||
|
||||
// For empty textareas, set minimum height
|
||||
if (textarea.value.trim() === "") {
|
||||
textarea.style.height = minHeight + "px";
|
||||
console.log("Empty textarea, setting height to:", minHeight + "px");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset to 1px to get accurate scrollHeight
|
||||
textarea.style.height = "1px";
|
||||
|
||||
// Set to content height (scrollHeight is the actual content height)
|
||||
const contentHeight = textarea.scrollHeight;
|
||||
const newHeight = Math.max(contentHeight, minHeight) + "px";
|
||||
console.log("Setting height to:", newHeight);
|
||||
textarea.style.height = newHeight;
|
||||
}
|
||||
|
||||
function NoEnters(event) {
|
||||
@@ -200,7 +248,12 @@ function HookupTextareaAutoResize(textarea) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset height on input
|
||||
// If browser supports field-sizing, CSS handles it
|
||||
if (supportsFieldSizing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: attach event listener for manual resizing
|
||||
textarea.addEventListener("input", () => {
|
||||
TextareaAutoResize(textarea);
|
||||
});
|
||||
@@ -239,18 +292,24 @@ function DisconnectNoEnters(textarea) {
|
||||
}
|
||||
|
||||
function MutateObserve(mutations, observer) {
|
||||
const needsJSResize = !supportsFieldSizing();
|
||||
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === "childList") {
|
||||
for (const node of mutation.addedNodes) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE && node.matches("textarea")) {
|
||||
HookupTextareaAutoResize(node);
|
||||
TextareaAutoResize(node);
|
||||
if (needsJSResize) {
|
||||
HookupTextareaAutoResize(node);
|
||||
TextareaAutoResize(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const node of mutation.removedNodes) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE && node.matches("textarea")) {
|
||||
DisconnectNoEnters(node);
|
||||
DisconnectTextareaAutoResize(node);
|
||||
if (needsJSResize) {
|
||||
DisconnectTextareaAutoResize(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,17 +318,33 @@ function MutateObserve(mutations, observer) {
|
||||
|
||||
// INFO: Various options and plugs for laoding and parsing forms.
|
||||
function FormLoad(form) {
|
||||
console.log("=== FormLoad CALLED ===");
|
||||
|
||||
if (!(form instanceof HTMLFormElement)) {
|
||||
console.warn("FormLoad: Provided element is not a form.");
|
||||
return;
|
||||
}
|
||||
|
||||
const textareas = document.querySelectorAll("textarea");
|
||||
console.log("Found", textareas.length, "textareas");
|
||||
|
||||
// Attach resize handler to all textareas
|
||||
for (const textarea of textareas) {
|
||||
HookupTextareaAutoResize(textarea);
|
||||
TextareaAutoResize(textarea);
|
||||
console.log("Attaching input listener to:", textarea.name || textarea.id);
|
||||
textarea.addEventListener('input', function() {
|
||||
console.log("Input event on textarea:", this.name || this.id);
|
||||
TextareaAutoResize(this);
|
||||
});
|
||||
}
|
||||
|
||||
// Initial resize after short delay (increased to ensure content is loaded)
|
||||
setTimeout(() => {
|
||||
console.log("Running initial textarea resize on", textareas.length, "textareas");
|
||||
for (const textarea of textareas) {
|
||||
TextareaAutoResize(textarea);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
const noEnterTextareas = document.querySelectorAll("textarea.no-enter");
|
||||
for (const textarea of noEnterTextareas) {
|
||||
HookupNoEnters(textarea);
|
||||
@@ -280,6 +355,34 @@ function FormLoad(form) {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
// Watch for class changes (hidden/visible) and resize textareas when they become visible
|
||||
const visibilityObserver = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === "attributes" && mutation.attributeName === "class") {
|
||||
const target = mutation.target;
|
||||
// Check if this element or its children contain textareas
|
||||
if (target instanceof HTMLElement) {
|
||||
const textareasInTarget = target.matches("textarea")
|
||||
? [target]
|
||||
: Array.from(target.querySelectorAll("textarea"));
|
||||
|
||||
for (const textarea of textareasInTarget) {
|
||||
// Only resize if now visible
|
||||
if (textarea.offsetParent !== null) {
|
||||
TextareaAutoResize(textarea);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
visibilityObserver.observe(form, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
@@ -301,5 +404,6 @@ window.SelectableInput = SelectableInput;
|
||||
window.PathPlusQuery = PathPlusQuery;
|
||||
window.HookupRBChange = HookupRBChange;
|
||||
window.FormLoad = FormLoad;
|
||||
window.TextareaAutoResize = TextareaAutoResize;
|
||||
|
||||
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor, SingleSelectRemote, AlmanachEditPage, RelationsEditor };
|
||||
|
||||
Reference in New Issue
Block a user