diff --git a/base/pom.xml b/base/pom.xml index 7da138a..d2e3426 100644 --- a/base/pom.xml +++ b/base/pom.xml @@ -5,7 +5,7 @@ com.flowingcode.vaadin.addons.demo commons-demo - 5.3.2-SNAPSHOT + 5.4.0-SNAPSHOT Commons Demo Common classes for add-ons demo @@ -317,6 +317,56 @@ + + dance + + + + org.apache.maven.plugins + maven-clean-plugin + 3.3.2 + + + + ${project.basedir} + + package.json + package-lock.json + tsconfig.json + tsconfig.json.* + types.d.ts + types.d.ts.* + vite.config.ts + vite.generated.ts + webpack.config.js + webpack.generated.js + + + + ${project.basedir}/frontend + + index.html + + + + ${project.basedir}/frontend/generated + + + ${project.basedir}/node_modules + + + ${project.basedir}/src/main/bundles + + + ${project.basedir}/src/main/dev-bundle + + + + + + + + diff --git a/base/src/main/java/com/flowingcode/vaadin/addons/demo/CommonsDemoIcons.java b/base/src/main/java/com/flowingcode/vaadin/addons/demo/CommonsDemoIcons.java new file mode 100644 index 0000000..49277fc --- /dev/null +++ b/base/src/main/java/com/flowingcode/vaadin/addons/demo/CommonsDemoIcons.java @@ -0,0 +1,78 @@ +/*- + * #%L + * Commons Demo + * %% + * Copyright (C) 2020 - 2025 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.demo; + +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.icon.IconFactory; +import java.util.Locale; + +/** + * CommonsDemo icons. + * + * @author Javier Godoy / Flowing Code + */ +public enum CommonsDemoIcons implements IconFactory { + ROTATE, FLIP, HIDE_SOURCE, SHOW_SOURCE; + + /** + * The Iconset name, i.e. {@code "fab"}." + */ + public static final String ICONSET = "commons-demo"; + + /** + * Return the full icon name. + * + * @return the full icon name, i.e. {@code "commons-demo:name"}.. + */ + public String getIconName() { + return ICONSET + ':' + getIconPart(); + } + + /** + * Return the icon name within the iconset. + * + * @return the icon name, i.e. {@code "name"}.. + */ + public String getIconPart() { + return name().toLowerCase(Locale.ENGLISH).replace('_', '-').replaceFirst("^-", ""); + } + + /** + * Create a new {@link Icon} instance with the icon determined by the name. + * + * @return a new instance of {@link Icon} component + */ + @Override + public Icon create() { + return new Icon(getIconPart()); + } + + /** + * Server side component for CommonsDemo icons. + */ + @JsModule("./commons-demo-iconset.ts") + @SuppressWarnings("serial") + public static final class Icon extends com.vaadin.flow.component.icon.Icon { + private Icon(String icon) { + super(ICONSET, icon); + } + } + +} diff --git a/base/src/main/java/com/flowingcode/vaadin/addons/demo/MultiSourceCodeViewer.java b/base/src/main/java/com/flowingcode/vaadin/addons/demo/MultiSourceCodeViewer.java index 1fccdc2..11baff5 100644 --- a/base/src/main/java/com/flowingcode/vaadin/addons/demo/MultiSourceCodeViewer.java +++ b/base/src/main/java/com/flowingcode/vaadin/addons/demo/MultiSourceCodeViewer.java @@ -76,6 +76,20 @@ public MultiSourceCodeViewer(List sourceCodeTabs, Map properties) { } public SourceCodeViewer(String url, String language, Map properties) { + addClassName("source-code-viewer"); + addClassName("has-code-viewer-gutter"); + codeViewer = new Element("code-viewer"); - getElement().appendChild(codeViewer); - getElement().getStyle().set("overflow", "auto"); - getElement().getStyle().set("display", "flex"); - codeViewer.getStyle().set("flex-grow", "1"); + + Div codeViewerWrapper = new Div(); + codeViewerWrapper.addClassName("source-code-viewer-codeviewer-wrapper"); + codeViewerWrapper.getElement().appendChild(codeViewer); + + // Non-scrolling overlay so the buttons stay pinned while the code scrolls + buttonsWrapper = new Div(); + buttonsWrapper.addClassName("source-code-viewer-buttons-wrapper"); + + add(codeViewerWrapper, buttonsWrapper); + setProperties(properties); - addAttachListener(ev -> fetchContents(url, language)); + addAttachListener(ev -> { + fetchContents(url, language); + observeScrollbar(); + }); + } + + /** + * Adds the overlay controls (show, hide, flip and rotate) pinned over the source code. + *

+ * The buttons do not change this viewer directly. Instead, each one dispatches a bubbling DOM + * event that is expected to be handled by an enclosing layout, which is what actually collapses, + * repositions or reorients the source panel: + *

    + *
  • show / hide dispatch {@code source-collapse-changed} (carrying {@code detail.collapsed}); + *
  • flip dispatches {@code source-flip}; + *
  • rotate dispatches {@code source-rotate}. + *
+ * The buttons are therefore only useful when this viewer is placed inside a layout that listens + * for those events and coordinates the response (see {@link TabbedDemo}); otherwise they emit + * events that nothing consumes. + *

+ * The controls are rendered by the {@code source-code-viewer-buttons} client-side web component, + * which dispatches the events locally on click; the enclosing layout (see {@link TabbedDemo}) + * handles them through Vaadin server-side listeners. This method is idempotent: the component is + * added only once. + */ + public void withButtons() { + if (buttonsWrapper.getElement().getChildCount() == 0) { + buttonsWrapper.getElement().appendChild(new Element("source-code-viewer-buttons")); + } + } + + /** + * Observes the scrollable wrapper. Whenever the vertical scrollbar appears or disappears, sets (or + * clears) the {@code --code-viewer-gutter} custom property on the nearest ancestor (or self) + * carrying the {@code has-code-viewer-gutter} class. Whenever the wrapper collapses below 24px in + * either axis, sets {@code --source-code-viewer-show-button-display} so the show button becomes + * visible (and clears it otherwise). + */ + private void observeScrollbar() { + getElement().executeJs( + """ + const root = this; + const wrapper = root.querySelector('.source-code-viewer-codeviewer-wrapper'); + if (!wrapper) return; + root.__scrollbarObserver?.disconnect(); + root.__scrollbarMutation?.disconnect(); + let hasScrollbar = null; + const update = () => { + if (wrapper.offsetWidth < 24 || wrapper.offsetHeight < 10) { + root.style.setProperty('--source-code-viewer-show-button-display', 'block'); + } else { + root.style.removeProperty('--source-code-viewer-show-button-display'); + } + const current = wrapper.scrollHeight > wrapper.clientHeight; + if (current === hasScrollbar) return; + hasScrollbar = current; + let target = root; + while (target && !target.classList.contains('has-code-viewer-gutter')) { + target = target.parentElement; + } + if (target) { + if (current) { + const scrollbarWidth = wrapper.offsetWidth - wrapper.clientWidth; + target.style.setProperty('--code-viewer-gutter', scrollbarWidth + 'px'); + } else { + target.style.removeProperty('--code-viewer-gutter'); + } + } + }; + let frame = 0; + const scheduleUpdate = () => { + if (frame) return; + frame = requestAnimationFrame(() => { frame = 0; update(); }); + }; + const resizeObserver = new ResizeObserver(scheduleUpdate); + resizeObserver.observe(wrapper); + root.__scrollbarObserver = resizeObserver; + const codeViewer = root.querySelector('code-viewer'); + if (codeViewer) { + const mutationObserver = new MutationObserver(scheduleUpdate); + mutationObserver.observe(codeViewer, {childList: true, subtree: true}); + root.__scrollbarMutation = mutationObserver; + } + update(); + """); + } + + private void dispatchEvent(ComponentEvent ev) { + for (Component c = this; c != null; c = c.getParent().orElse(null)) { + ComponentUtil.fireEvent(c, ev); + } } public void fetchContents(String url, String language) { diff --git a/base/src/main/java/com/flowingcode/vaadin/addons/demo/SplitLayoutDemo.java b/base/src/main/java/com/flowingcode/vaadin/addons/demo/SplitLayoutDemo.java index 5d83475..d093d54 100644 --- a/base/src/main/java/com/flowingcode/vaadin/addons/demo/SplitLayoutDemo.java +++ b/base/src/main/java/com/flowingcode/vaadin/addons/demo/SplitLayoutDemo.java @@ -48,7 +48,7 @@ public SplitLayoutDemo(Component demo, List tabs) { properties.put("vaadin", VaadinVersion.getVaadinVersion()); properties.put("flow", Version.getFullVersion()); - code = new MultiSourceCodeViewer(tabs, properties); + code = new MultiSourceCodeViewer(tabs, properties).withButtons(); this.demo = demo; setSourcePosition(code.getSourcePosition()); @@ -59,7 +59,11 @@ public boolean isEmpty() { return code.isEmpty(); } - private void setSourcePosition(SourcePosition position) { + public SourcePosition getSourcePosition() { + return sourcePosition; + } + + public void setSourcePosition(SourcePosition position) { if (!position.equals(sourcePosition)) { getContent().removeAll(); switch (position) { @@ -76,10 +80,6 @@ private void setSourcePosition(SourcePosition position) { } } - public void toggleSourcePosition() { - setSourcePosition(sourcePosition.toggle()); - } - public void setOrientation(Orientation o) { getContent().setOrientation(o); getContent() @@ -93,26 +93,22 @@ public Orientation getOrientation() { return getContent().getOrientation(); } - public void setSplitterPosition(int pos) { - getContent().setSplitterPosition(pos); - } - public void setSizeFull() { getContent().setSizeFull(); } - public void showSourceCode() { - getContent().setSplitterPosition(50); - } - - public void hideSourceCode() { - switch (sourcePosition) { - case PRIMARY: - getContent().setSplitterPosition(0); - break; - case SECONDARY: - getContent().setSplitterPosition(100); - break; + public void setSourceCollapsed(boolean collapsed) { + if (!collapsed) { + getContent().setSplitterPosition(50); + } else { + switch (sourcePosition) { + case PRIMARY: + getContent().setSplitterPosition(0); + break; + case SECONDARY: + getContent().setSplitterPosition(100); + break; + } } } diff --git a/base/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java b/base/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java index 6505f93..267791b 100644 --- a/base/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java +++ b/base/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java @@ -20,6 +20,9 @@ package com.flowingcode.vaadin.addons.demo; import com.flowingcode.vaadin.addons.GithubBranch; +import com.flowingcode.vaadin.addons.demo.events.OrientationChangedEvent; +import com.flowingcode.vaadin.addons.demo.events.SourceCollapseChangedEvent; +import com.flowingcode.vaadin.addons.demo.events.SourcePositionChangedEvent; import com.vaadin.flow.component.AttachEvent; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentEventListener; @@ -70,14 +73,11 @@ public class TabbedDemo extends VerticalLayout implements RouterLayout { private static final int MOBILE_DEVICE_BREAKPOINT_WIDTH = 768; private boolean autoVisibility; + private boolean sourceCollapsed; private EnhancedRouteTabs tabs; private HorizontalLayout footer; private SplitLayoutDemo currentLayout; - private Checkbox orientationCB; - private Checkbox codeCB; - private Checkbox codePositionCB; private Checkbox themeCB; - private Orientation splitOrientation; private Button helperButton; private DemoHelperViewer demoHelperViewer; @@ -89,23 +89,14 @@ public TabbedDemo() { tabs = new EnhancedRouteTabs(); - // Footer - orientationCB = new Checkbox("Toggle Orientation"); - orientationCB.setValue(true); - orientationCB.addClassName("smallcheckbox"); - orientationCB.addValueChangeListener(ev -> { - if (ev.isFromClient()) { - toggleSplitterOrientation(); - } + // The source controls live inside SourceCodeViewer and signal across components + // via bubbling DOM events; collapse carries data, so it uses SourceCollapseChangedEvent. + addSourceCollapseListener(ev -> { + sourceCollapsed = ev.isCollapsed(); + updateSplitterPosition(); }); - codeCB = new Checkbox("Show Source Code"); - codeCB.setValue(true); - codeCB.addClassName("smallcheckbox"); - codeCB.addValueChangeListener(ev -> updateSplitterPosition()); - codePositionCB = new Checkbox("Toggle Code Position"); - codePositionCB.setValue(true); - codePositionCB.addClassName("smallcheckbox"); - codePositionCB.addValueChangeListener(ev -> toggleSourcePosition()); + getElement().addEventListener("source-flip", ev -> toggleSourcePosition(true)); + getElement().addEventListener("source-rotate", ev -> toggleSplitterOrientation(true)); themeCB = new Checkbox("Dark Theme"); themeCB.setValue(false); themeCB.addClassName("smallcheckbox"); @@ -117,7 +108,7 @@ public TabbedDemo() { footer = new HorizontalLayout(); footer.setWidthFull(); footer.setJustifyContentMode(JustifyContentMode.END); - footer.add(codeCB, codePositionCB, orientationCB, themeCB); + footer.add(themeCB); footer.setClassName("demo-footer"); Package pkg = this.getClass().getPackage(); @@ -231,6 +222,11 @@ public void showRouterLayoutContent(HasElement content) { createSourceCodeTab(demo.getClass(), demoSource).ifPresent(sourceTabs::add); } + Orientation splitOrientation = null; + if (currentLayout != null) { + splitOrientation = currentLayout.getOrientation(); + } + if (!sourceTabs.isEmpty()) { currentLayout = new SplitLayoutDemo(demo, sourceTabs); if (currentLayout.isEmpty()) { @@ -334,13 +330,7 @@ public void removeRouterLayoutContent(HasElement oldContent) { private void updateSplitterPosition() { if (currentLayout != null) { - if (codeCB.getValue()) { - currentLayout.showSourceCode(); - } else { - currentLayout.hideSourceCode(); - } - orientationCB.setEnabled(codeCB.getValue()); - codePositionCB.setEnabled(codeCB.getValue()); + currentLayout.setSourceCollapsed(sourceCollapsed); } } @@ -350,29 +340,50 @@ private void updateSplitterPosition() { * @param visible {@code true} to make the source code visible, {@code false} otherwise */ public void setSourceVisible(boolean visible) { - codeCB.setValue(visible); - codePositionCB.setVisible(visible); + fireSourceCollapseChangedEvent(!visible, false); } /** * Toggles the position of the source code relative to the demo content. */ public void toggleSourcePosition() { + toggleSourcePosition(false); + } + + private void toggleSourcePosition(boolean fromClient) { if (currentLayout != null) { - currentLayout.toggleSourcePosition(); + setSourcePosition(currentLayout.getSourcePosition().toggle(), fromClient); } } - private void toggleSplitterOrientation() { + /** + * Sets the position of the source code relative to the demo content. + * + * @param sourcePosition the new source position + */ + public void setSourcePosition(SourcePosition sourcePosition) { + setSourcePosition(sourcePosition, false); + } + + private void setSourcePosition(SourcePosition sourcePosition, boolean fromClient) { + if (currentLayout != null) { + currentLayout.setSourcePosition(sourcePosition); + fireSourcePositionChangedEvent(sourcePosition, fromClient); + } + } + + private void toggleSplitterOrientation(boolean fromClient) { if (currentLayout == null) { return; } + + Orientation splitOrientation = getOrientation(); if (Orientation.HORIZONTAL.equals(splitOrientation)) { splitOrientation = Orientation.VERTICAL; } else { splitOrientation = Orientation.HORIZONTAL; } - setOrientation(splitOrientation); + setOrientation(splitOrientation, fromClient); } /** @@ -390,12 +401,15 @@ public Orientation getOrientation() { * @param orientation the new orientation */ public void setOrientation(Orientation orientation) { - splitOrientation = orientation; - if (currentLayout != null) { - currentLayout.setOrientation(orientation); - currentLayout.setSplitterPosition(50); + setOrientation(orientation, false); + } + + private void setOrientation(Orientation orientation, boolean fromClient) { + if (currentLayout != null && orientation != getOrientation()) { + currentLayout.setOrientation(orientation); + currentLayout.setSourceCollapsed(false); + fireOrientationChangedEvent(orientation, fromClient); } - orientationCB.setValue(Orientation.HORIZONTAL.equals(orientation)); } /** @@ -491,9 +505,6 @@ private static Stream collectThemeChangeObservers(Component private void updateFooterButtonsVisibility() { boolean hasSourceCode = currentLayout != null; ComponentUtil.fireEvent(this, new TabbedDemoSourceEvent(this, hasSourceCode)); - orientationCB.setVisible(hasSourceCode); - codeCB.setVisible(hasSourceCode); - codePositionCB.setVisible(hasSourceCode); } /** @@ -512,8 +523,7 @@ protected void onAttach(AttachEvent attachEvent) { super.onAttach(attachEvent); getUI().ifPresent(ui -> ui.getPage().retrieveExtendedClientDetails(receiver -> { boolean mobile = receiver.getBodyClientWidth() <= MOBILE_DEVICE_BREAKPOINT_WIDTH; - codeCB.setValue(codeCB.getValue() && !mobile); - codePositionCB.setValue(codeCB.getValue() && !mobile); + fireSourceCollapseChangedEvent(sourceCollapsed || mobile, false); boolean portraitOrientation = receiver.getBodyClientHeight() > receiver.getBodyClientWidth(); adjustSplitOrientation(portraitOrientation); @@ -522,11 +532,10 @@ protected void onAttach(AttachEvent attachEvent) { private void adjustSplitOrientation(boolean portraitOrientation) { if (portraitOrientation) { - splitOrientation = Orientation.VERTICAL; + setOrientation(Orientation.VERTICAL); } else { - splitOrientation = Orientation.HORIZONTAL; + setOrientation(Orientation.HORIZONTAL); } - setOrientation(splitOrientation); } /** @@ -557,4 +566,46 @@ private void setupDemoHelperButton(Class helperClass) { } } + /** + * Adds a listener for {@link SourceCollapseChangedEvent}. + * + * @param listener the listener to add + */ + public void addSourceCollapseListener( + ComponentEventListener listener) { + ComponentUtil.addListener(this, SourceCollapseChangedEvent.class, listener); + } + + /** + * Adds a listener for {@link SourcePositionChangedEvent}. + * + * @param listener the listener to add + */ + public void addSourcePositionChangedListener( + ComponentEventListener listener) { + ComponentUtil.addListener(this, SourcePositionChangedEvent.class, listener); + } + + /** + * Adds a listener for {@link OrientationChangedEvent}. + * + * @param listener the listener to add + */ + public void addOrientationChangedListener( + ComponentEventListener listener) { + ComponentUtil.addListener(this, OrientationChangedEvent.class, listener); + } + + private void fireSourceCollapseChangedEvent(boolean collapsed, boolean fromClient) { + fireEvent(new SourceCollapseChangedEvent(this, fromClient, collapsed)); + } + + private void fireSourcePositionChangedEvent(SourcePosition sourcePosition, boolean fromClient) { + fireEvent(new SourcePositionChangedEvent(this, fromClient, sourcePosition)); + } + + private void fireOrientationChangedEvent(Orientation orientation, boolean fromClient) { + fireEvent(new OrientationChangedEvent(this, fromClient, orientation)); + } + } diff --git a/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/OrientationChangedEvent.java b/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/OrientationChangedEvent.java new file mode 100644 index 0000000..ba79252 --- /dev/null +++ b/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/OrientationChangedEvent.java @@ -0,0 +1,38 @@ +/*- + * #%L + * Commons Demo + * %% + * Copyright (C) 2020 - 2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.demo.events; + +import com.flowingcode.vaadin.addons.demo.TabbedDemo; +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.splitlayout.SplitLayout.Orientation; +import lombok.Getter; + +@SuppressWarnings("serial") +public class OrientationChangedEvent extends ComponentEvent { + + @Getter + private Orientation orientation; + + public OrientationChangedEvent(TabbedDemo source, boolean fromClient, Orientation orientation) { + super(source, fromClient); + this.orientation = orientation; + } + +} diff --git a/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/SourceCollapseChangedEvent.java b/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/SourceCollapseChangedEvent.java new file mode 100644 index 0000000..e95daa9 --- /dev/null +++ b/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/SourceCollapseChangedEvent.java @@ -0,0 +1,41 @@ +/*- + * #%L + * Commons Demo + * %% + * Copyright (C) 2020 - 2025 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.demo.events; + +import com.flowingcode.vaadin.addons.demo.TabbedDemo; +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.DomEvent; +import com.vaadin.flow.component.EventData; +import lombok.Getter; + +@SuppressWarnings("serial") +@DomEvent("source-collapse-changed") +public class SourceCollapseChangedEvent extends ComponentEvent { + + @Getter + private boolean collapsed; + + public SourceCollapseChangedEvent(TabbedDemo source, boolean fromClient, + @EventData("event.detail.collapsed") boolean collapsed) { + super(source, fromClient); + this.collapsed = collapsed; + } + +} \ No newline at end of file diff --git a/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/SourcePositionChangedEvent.java b/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/SourcePositionChangedEvent.java new file mode 100644 index 0000000..b4b7d7a --- /dev/null +++ b/base/src/main/java/com/flowingcode/vaadin/addons/demo/events/SourcePositionChangedEvent.java @@ -0,0 +1,39 @@ +/*- + * #%L + * Commons Demo + * %% + * Copyright (C) 2020 - 2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.demo.events; + +import com.flowingcode.vaadin.addons.demo.SourcePosition; +import com.flowingcode.vaadin.addons.demo.TabbedDemo; +import com.vaadin.flow.component.ComponentEvent; +import lombok.Getter; + +@SuppressWarnings("serial") +public class SourcePositionChangedEvent extends ComponentEvent { + + @Getter + private SourcePosition sourcePosition; + + public SourcePositionChangedEvent(TabbedDemo source, boolean fromClient, + SourcePosition sourcePosition) { + super(source, fromClient); + this.sourcePosition = sourcePosition; + } + +} diff --git a/base/src/main/resources/META-INF/resources/frontend/commons-demo-iconset.ts b/base/src/main/resources/META-INF/resources/frontend/commons-demo-iconset.ts new file mode 100644 index 0000000..ba77586 --- /dev/null +++ b/base/src/main/resources/META-INF/resources/frontend/commons-demo-iconset.ts @@ -0,0 +1,124 @@ +/*- + * #%L + * Commons Demo + * %% + * Copyright (C) 2020 - 2025 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +import '@vaadin/icon/vaadin-icon.js'; +import { Iconset } from '@vaadin/icon/vaadin-iconset.js'; +import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +/** +ISC License + +Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). +All other copyright (c) for Lucide are held by Lucide Contributors 2025. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The MIT License (MIT) (for portions derived from Feather) + +Copyright (c) 2013-2023 Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com +License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) +Copyright 2024 Fonticons, Inc. + +--- + +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** + +*/ + +registerStyles( + 'vaadin-button', + css` + [part] ::slotted(vaadin-icon[icon^='commons-demo:']), [part] ::slotted(iron-icon[icon^='commons-demo:']) + { + padding: 0.25em; + box-sizing: border-box !important; + }`, +); + +const template = document.createElement('template'); +template.innerHTML = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +customElements.whenDefined('vaadin-iconset').then(Iconset=>{ + Iconset.register('commons-demo', 24, template); +}); diff --git a/base/src/main/resources/META-INF/resources/frontend/source-code-viewer-buttons.ts b/base/src/main/resources/META-INF/resources/frontend/source-code-viewer-buttons.ts new file mode 100644 index 0000000..f83182f --- /dev/null +++ b/base/src/main/resources/META-INF/resources/frontend/source-code-viewer-buttons.ts @@ -0,0 +1,70 @@ +/*- + * #%L + * Commons Demo + * %% + * Copyright (C) 2020 - 2026 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +import { + html, + LitElement +} from "lit-element"; + +import {customElement} from 'lit/decorators/custom-element.js'; +import '@vaadin/button/vaadin-button.js'; +import './commons-demo-iconset.js'; + +/** + * Overlay controls pinned over the source code. Each button dispatches a bubbling DOM event that is + * handled by an enclosing layout (see TabbedDemo), which is what actually collapses, repositions or + * reorients the source panel. Firing the events on the client avoids a server roundtrip on click. + */ +@customElement("source-code-viewer-buttons") +export class SourceCodeViewerButtons extends LitElement { + + // Render in light DOM so the shared stylesheet (shared-styles.css) styles the buttons. + createRenderRoot() { + return this; + } + + private fire(type: string, detail?: any) { + this.dispatchEvent(new CustomEvent(type, {bubbles: true, detail})); + } + + render() { + return html` + this.fire('source-collapse-changed', {collapsed: false})}> + + + this.fire('source-collapse-changed', {collapsed: true})}> + + + this.fire('source-flip')}> + + + this.fire('source-rotate')}> + + + `; + } +} diff --git a/base/src/main/resources/META-INF/resources/frontend/styles/commons-demo/shared-styles.css b/base/src/main/resources/META-INF/resources/frontend/styles/commons-demo/shared-styles.css index 6fef4f7..be7f6fb 100644 --- a/base/src/main/resources/META-INF/resources/frontend/styles/commons-demo/shared-styles.css +++ b/base/src/main/resources/META-INF/resources/frontend/styles/commons-demo/shared-styles.css @@ -51,5 +51,122 @@ code-highlighter code { cursor: pointer; } +.source-code-viewer { + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; +} + +.source-code-viewer-codeviewer-wrapper { + display: flex; + flex-grow: 1; + overflow: auto; +} + +.source-code-viewer-codeviewer-wrapper > code-viewer { + flex-grow: 1; +} + +.source-code-viewer-buttons-wrapper { + position: absolute; + inset: 0; + pointer-events: none; +} + +source-code-viewer-buttons { + position: absolute; + z-index: 1; + right: calc(0.25rem + var(--code-viewer-gutter, 0px)); + top: 0.25rem; + display: flex; + gap: var(--lumo-space-xs, 0.25rem); + container-type: inline-size; + width: 100%; + justify-content: end; + pointer-events: none; +} + +vaadin-button.source-code-viewer-button { + color: color-mix(in srgb, #f8f8f2 70%, transparent); + pointer-events: auto; + background: transparent; + display: none; + padding: 0; +} + +vaadin-button.source-code-viewer-button vaadin-icon { + --vaadin-icon-size: 20px; + padding: 0.125em; +} + +@container style(--lumo-size-m) { + vaadin-button.source-code-viewer-button vaadin-icon { + padding: 0.25em; + } +} + + +vaadin-button.source-code-viewer-show-button { + display: var(--source-code-viewer-show-button-display, none); + color: var(--lumo-body-text-color, var(--vaadin-text-color)); + position: fixed; +} + +[orientation="horizontal"] [slot="primary"] vaadin-button.source-code-viewer-show-button { + right: 0; +} + +[orientation="horizontal"] [slot="secondary"] vaadin-button.source-code-viewer-show-button { + right: 0.5rem; +} + +[orientation="vertical"] [slot="primary"] vaadin-button.source-code-viewer-show-button { + top: 0.5rem; +} + +[orientation="vertical"] [slot="secondary"] vaadin-button.source-code-viewer-show-button { + top: 0; +} + +/* Rotate the icons to match the layout disposition. */ +[orientation="horizontal"] [slot="secondary"] vaadin-button.source-code-viewer-button{ + transform: rotate(0deg); + &.source-code-viewer-rotate-button { transform: scaleX(-1) rotate(90deg); } +} + +[orientation="horizontal"] [slot="primary"] vaadin-button.source-code-viewer-button { + transform: rotate(180deg); + &.source-code-viewer-rotate-button { transform: rotate(0deg); } +} + +[orientation="vertical"] [slot="secondary"] vaadin-button.source-code-viewer-button { + transform: rotate(90deg); + &.source-code-viewer-rotate-button { transform: rotate(0deg); } +} + +[orientation="vertical"] [slot="primary"] vaadin-button.source-code-viewer-button { + transform: rotate(270deg); + &.source-code-viewer-rotate-button { transform: scaleX(-1) rotate(90deg); } +} + +@container (min-width: 82px) { + vaadin-button.source-code-viewer-flip-button { + display: block; + } +} + +@container (min-width: 53px) { + vaadin-button.source-code-viewer-rotate-button { + display: block; + } +} + +@container (min-width: 24px) { + vaadin-button.source-code-viewer-hide-button { + display: block; + } +} + .commons-demo-split-layout { overflow: hidden } .commons-demo-split-layout > [slot] { overflow: auto; } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9e9e1af..362867d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.flowingcode.vaadin.addons.demo commons-demo-aggregator - 5.3.2-SNAPSHOT + 5.4.0-SNAPSHOT pom Commons Demo Aggregator diff --git a/processor/pom.xml b/processor/pom.xml index fbd8a0c..48f9113 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -5,7 +5,7 @@ com.flowingcode.vaadin.addons.demo commons-demo-processor - 5.3.2-SNAPSHOT + 5.4.0-SNAPSHOT Commons Demo Processor Annotation processor for Commons Demo: copies @DemoSource-referenced files into the class output @@ -23,7 +23,6 @@ UTF-8 17 17 - 5.3.2-SNAPSHOT @@ -50,7 +49,7 @@ com.flowingcode.vaadin.addons.demo commons-demo - ${commons-demo.version} + ${project.version} provided